mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-12-12 10:55:20 +02:00
0ad38b8621
Provide a timeout parameters to malwarescan step. This is forwarded to the piper http layer. The default used there is 10 seconds with is not useable for that use case for larger files.
249 lines
8.6 KiB
Go
249 lines
8.6 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
piperhttp "github.com/SAP/jenkins-library/pkg/http"
|
|
"github.com/stretchr/testify/assert"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
var malwareScanConfig = malwareExecuteScanOptions{
|
|
Host: "https://example.org/malwarescanner",
|
|
Username: "me",
|
|
Password: "********",
|
|
File: "target/myFile",
|
|
Timeout: "60",
|
|
}
|
|
|
|
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
|
|
}
|