2020-04-23 09:12:10 +02:00
|
|
|
package cmd
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
piperhttp "github.com/SAP/jenkins-library/pkg/http"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
)
|
|
|
|
|
2020-05-06 08:52:26 +02:00
|
|
|
var malwareScanConfig = malwareExecuteScanOptions{Host: "https://example.org/malwarescanner", Username: "me", Password: "********", File: "target/myFile"}
|
2020-04-23 09:12:10 +02:00
|
|
|
|
|
|
|
func TestMalwareScanTests(t *testing.T) {
|
|
|
|
|
|
|
|
open = openFileMock()
|
|
|
|
|
|
|
|
getSHA256 = func(path string) (string, error) {
|
|
|
|
return "96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85", nil
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
open = _open
|
|
|
|
getSHA256 = _getSHA256
|
|
|
|
}()
|
|
|
|
|
|
|
|
t.Run("No malware, no encrypted content", func(t *testing.T) {
|
|
|
|
httpClient := httpMock{StatusCode: 200, ResponseBody: "{\"malwareDetected\":false,\"encryptedContentDetected\":false,\"scanSize\":298782,\"mimeType\":\"application/octet-stream\",\"SHA256\":\"96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85\"}"}
|
|
|
|
|
|
|
|
error := runMalwareScan(&malwareScanConfig, nil, nil, &httpClient)
|
|
|
|
|
|
|
|
if assert.NoError(t, error) {
|
|
|
|
|
|
|
|
t.Run("check url", func(t *testing.T) {
|
|
|
|
assert.Equal(t, "https://example.org/malwarescanner/scan", httpClient.URL)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("check method", func(t *testing.T) {
|
|
|
|
assert.Equal(t, "POST", httpClient.Method)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("check user", func(t *testing.T) {
|
|
|
|
assert.Equal(t, "me", httpClient.Options.Username)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("check password", func(t *testing.T) {
|
|
|
|
assert.Equal(t, "********", httpClient.Options.Password)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Malware detected", func(t *testing.T) {
|
|
|
|
|
|
|
|
httpClient := httpMock{StatusCode: 200, ResponseBody: "{\"malwareDetected\":true,\"encryptedContentDetected\":false,\"scanSize\":298782,\"mimeType\":\"application/octet-stream\",\"SHA256\":\"96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85\"}"}
|
|
|
|
error := runMalwareScan(&malwareScanConfig, nil, nil, &httpClient)
|
|
|
|
assert.EqualError(t, error, "Malware scan failed for file 'target/myFile'. Malware detected: true, encrypted content detected: false")
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Encrypted content detected", func(t *testing.T) {
|
|
|
|
|
|
|
|
httpClient := httpMock{StatusCode: 200, ResponseBody: "{\"malwareDetected\": false, \"encryptedContentDetected\": true, \"scanSize\":298782,\"mimeType\":\"application/octet-stream\",\"SHA256\":\"96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85\"}"}
|
|
|
|
error := runMalwareScan(&malwareScanConfig, nil, nil, &httpClient)
|
|
|
|
assert.EqualError(t, error, "Malware scan failed for file 'target/myFile'. Malware detected: false, encrypted content detected: true")
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Malware and encrypted content detected", func(t *testing.T) {
|
|
|
|
|
|
|
|
httpClient := httpMock{StatusCode: 200, ResponseBody: "{\"malwareDetected\": true, \"encryptedContentDetected\": true, \"scanSize\":298782,\"mimeType\":\"application/octet-stream\",\"SHA256\":\"96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85\"}"}
|
|
|
|
error := runMalwareScan(&malwareScanConfig, nil, nil, &httpClient)
|
|
|
|
assert.EqualError(t, error, "Malware scan failed for file 'target/myFile'. Malware detected: true, encrypted content detected: true")
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestMalwareScanRequest(t *testing.T) {
|
|
|
|
|
|
|
|
httpClient := httpMock{StatusCode: 200, ResponseBody: "{\"malwareDetected\":false,\"encryptedContentDetected\":false,\"scanSize\":298782,\"mimeType\":\"application/octet-stream\",\"SHA256\":\"96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85\"}"}
|
|
|
|
candidate := readCloserMock{Content: "HELLO"}
|
|
|
|
|
|
|
|
malWareScanResponse, err := sendMalwareScanRequest(&httpClient, "POST", "https://example.org/malwarescanner", candidate)
|
|
|
|
|
|
|
|
t.Run("Malware - scanRequest - check response body closed", func(t *testing.T) {
|
|
|
|
if assert.NoError(t, err) {
|
|
|
|
assert.True(t, httpClient.Body.Closed)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Malware - scanRequest - check scan response", func(t *testing.T) {
|
|
|
|
|
|
|
|
if assert.NoError(t, err) {
|
|
|
|
assert.False(t, malWareScanResponse.MalwareDetected)
|
|
|
|
assert.False(t, malWareScanResponse.EncryptedContentDetected)
|
|
|
|
assert.Equal(t, 298782, malWareScanResponse.ScanSize)
|
|
|
|
assert.Equal(t, "96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85", malWareScanResponse.SHA256)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestMalwareMarshalBody(t *testing.T) {
|
|
|
|
|
|
|
|
t.Run("Malware - marshalResponse - body uninitialized", func(t *testing.T) {
|
|
|
|
var body []byte
|
|
|
|
r, error := marshalResponse(body)
|
|
|
|
assert.Nil(t, r)
|
|
|
|
assert.EqualError(t, error, "Unmarshalling of response body failed. Body: '': unexpected end of JSON input")
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestMalwareValidateResponse(t *testing.T) {
|
|
|
|
|
|
|
|
t.Run("Malware - validateResponse - HTTP error", func(t *testing.T) {
|
|
|
|
r, e := validateResponse(nil, fmt.Errorf("request error"))
|
|
|
|
assert.Nil(t, r)
|
|
|
|
assert.EqualError(t, e, "HTTP request failed with error: request error. Details: \"\"")
|
|
|
|
})
|
|
|
|
t.Run("Malware - validateResponse - empty response", func(t *testing.T) {
|
|
|
|
r, e := validateResponse(&http.Response{}, fmt.Errorf("request error"))
|
|
|
|
assert.Nil(t, r)
|
|
|
|
assert.EqualError(t, e, "HTTP request failed with error: request error. Details: \"\"")
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Malware - validateResponse - status code 500", func(t *testing.T) {
|
|
|
|
mock := responseMock(500, true)
|
|
|
|
r, e := validateResponse(mock, nil)
|
|
|
|
assert.Nil(t, r)
|
|
|
|
assert.True(t, strings.HasPrefix(e.Error(), "Unexpected response code (500)"))
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Malware - validateResponse - malwareDetected", func(t *testing.T) {
|
|
|
|
mock := responseMock(200, true)
|
|
|
|
r, e := validateResponse(mock, nil)
|
|
|
|
assert.NotNil(t, r)
|
|
|
|
assert.Nil(t, e)
|
|
|
|
})
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func responseMock(code int, malwareDetected bool) *http.Response {
|
|
|
|
if malwareDetected {
|
|
|
|
return &http.Response{StatusCode: code, Body: &readCloserMock{Content: "{\"malwareDetected\":true,\"encryptedContentDetected\":false,\"scanSize\":298782,\"mimeType\":\"application/octet-stream\",\"SHA256\":\"96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85\"}"}}
|
|
|
|
}
|
|
|
|
return &http.Response{StatusCode: code, Body: &readCloserMock{Content: "{\"malwareDetected\":false,\"encryptedContentDetected\":false,\"scanSize\":298782,\"mimeType\":\"application/octet-stream\",\"SHA256\":\"96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85\"}"}}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestMalwareFileNotFound(t *testing.T) {
|
|
|
|
|
|
|
|
httpClient := httpMock{StatusCode: 200}
|
|
|
|
|
|
|
|
open = func(path string) (io.ReadCloser, error) {
|
|
|
|
return nil, fmt.Errorf("open %s: no such file or directory", path)
|
|
|
|
}
|
|
|
|
defer func() { open = _open }()
|
|
|
|
error := runMalwareScan(&malwareScanConfig, nil, nil, &httpClient)
|
|
|
|
|
|
|
|
assert.Error(t, error, "open target/myFile: no such file or directory")
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestMalwareInternalServerError(t *testing.T) {
|
|
|
|
|
|
|
|
httpClient := httpMock{StatusCode: 500, ResponseBody: "internal server error"}
|
|
|
|
|
|
|
|
open = openFileMock()
|
|
|
|
|
|
|
|
defer func() { open = _open }()
|
|
|
|
|
|
|
|
err := runMalwareScan(&malwareScanConfig, nil, nil, &httpClient)
|
|
|
|
|
|
|
|
assert.Error(t, err, "Unexpected response code (500). 200 expected. Details: \"internal server error\"")
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestMalwareRetrieveHash(t *testing.T) {
|
|
|
|
|
|
|
|
open = openFileMock()
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
open = _open
|
|
|
|
}()
|
|
|
|
hash, err := _getSHA256("target/myFile")
|
|
|
|
|
|
|
|
if assert.NoError(t, err) {
|
|
|
|
assert.Equal(t, "3733cd977ff8eb18b987357e22ced99f46097f31ecb239e878ae63760e83e4d5", hash)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func openFileMock() func(path string) (io.ReadCloser, error) {
|
|
|
|
return func(path string) (io.ReadCloser, error) {
|
|
|
|
if path == "target/myFile" {
|
|
|
|
return &readCloserMock{Content: "HELLO"}, nil // the content does not matter
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("open %s: no such file or directory", path)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|