mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-12-14 11:03:09 +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.
205 lines
5.5 KiB
Go
205 lines
5.5 KiB
Go
package cmd
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/SAP/jenkins-library/pkg/command"
|
|
piperhttp "github.com/SAP/jenkins-library/pkg/http"
|
|
"github.com/SAP/jenkins-library/pkg/log"
|
|
"github.com/SAP/jenkins-library/pkg/telemetry"
|
|
"github.com/pkg/errors"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
)
|
|
|
|
var open = _open
|
|
var getSHA256 = _getSHA256
|
|
|
|
func _open(path string) (io.ReadCloser, error) {
|
|
return os.Open(path)
|
|
}
|
|
|
|
type malwareExecuteScanResponse struct {
|
|
MalwareDetected bool
|
|
EncryptedContentDetected bool
|
|
ScanSize int
|
|
MimeType string
|
|
SHA256 string
|
|
}
|
|
|
|
func malwareExecuteScan(config malwareExecuteScanOptions, telemetryData *telemetry.CustomData) {
|
|
// for command execution use Command
|
|
c := command.Command{}
|
|
// reroute command output to logging framework
|
|
c.Stdout(log.Writer())
|
|
c.Stderr(log.Writer())
|
|
|
|
// for http calls import piperhttp "github.com/SAP/jenkins-library/pkg/http"
|
|
// and use a &piperhttp.Client{} in a custom system
|
|
// Example: step checkmarxExecuteScan.go
|
|
|
|
httpClient := &piperhttp.Client{}
|
|
|
|
// error situations should stop execution through log.Entry().Fatal() call which leads to an os.Exit(1) in the end
|
|
err := runMalwareScan(&config, telemetryData, &c, httpClient)
|
|
if err != nil {
|
|
log.Entry().WithError(err).Fatal("step execution failed")
|
|
}
|
|
}
|
|
|
|
func runMalwareScan(config *malwareExecuteScanOptions, telemetryData *telemetry.CustomData, command execRunner,
|
|
httpClient piperhttp.Sender) error {
|
|
|
|
log.Entry().Infof("Scanning file \"%s\" for malware using service \"%s\"", config.File, config.Host)
|
|
|
|
candidate, err := open(config.File)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer candidate.Close()
|
|
|
|
timeout, err := time.ParseDuration(fmt.Sprintf("%ss", config.Timeout))
|
|
if err != nil {
|
|
return errors.Wrapf(err, "Invalid timeout: %v", config.Timeout)
|
|
}
|
|
|
|
opts := piperhttp.ClientOptions{
|
|
Username: config.Username,
|
|
Password: config.Password,
|
|
MaxRequestDuration: timeout,
|
|
TransportTimeout: timeout,
|
|
}
|
|
httpClient.SetOptions(opts)
|
|
|
|
var scanResponse *malwareExecuteScanResponse
|
|
scanResponse, err = sendMalwareScanRequest(httpClient, "POST", config.Host+"/scan", candidate)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Entry().Debugf(
|
|
"File '%s' has been scanned. MalwareDetected: %t, EncryptedContentDetected: %t, ScanSize: %d, MimeType: '%s', SHA256: '%s'",
|
|
config.File,
|
|
scanResponse.MalwareDetected,
|
|
scanResponse.EncryptedContentDetected,
|
|
scanResponse.ScanSize,
|
|
scanResponse.MimeType,
|
|
scanResponse.SHA256)
|
|
|
|
err = validateHash(scanResponse.SHA256, config.File)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if scanResponse.MalwareDetected || scanResponse.EncryptedContentDetected {
|
|
return fmt.Errorf("Malware scan failed for file '%s'. Malware detected: %t, encrypted content detected: %t",
|
|
config.File, scanResponse.MalwareDetected, scanResponse.EncryptedContentDetected)
|
|
}
|
|
|
|
log.Entry().Infof("Malware scan succeeded for file '%s'. Malware detected: %t, encrypted content detected: %t",
|
|
config.File, scanResponse.MalwareDetected, scanResponse.EncryptedContentDetected)
|
|
|
|
return nil
|
|
}
|
|
|
|
func sendMalwareScanRequest(client piperhttp.Sender, method, url string, candidate io.Reader) (*malwareExecuteScanResponse, error) {
|
|
|
|
// piper http utils mashall some http response codes into errors. We wan't to check the status code
|
|
// ourselvs hence we wait with returning that error (maybe also related to errors others than http status codes)
|
|
|
|
// sendRequest results in any combination of nil and non-nil response and error.
|
|
// a response body could even be already closed.
|
|
response, err := client.SendRequest(method, url, candidate, prepareHeaders(), nil)
|
|
|
|
if response != nil && response.Body != nil {
|
|
defer response.Body.Close()
|
|
}
|
|
|
|
return validateResponse(response, err)
|
|
}
|
|
|
|
func validateResponse(response *http.Response, err error) (*malwareExecuteScanResponse, error) {
|
|
|
|
var body []byte
|
|
var errRead error
|
|
if response != nil && response.Body != nil {
|
|
body, errRead = ioutil.ReadAll(response.Body)
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("HTTP request failed with error: %v. Details: \"%s\"", err, body)
|
|
}
|
|
|
|
if response == nil {
|
|
return nil, fmt.Errorf("No response available")
|
|
}
|
|
|
|
if response.StatusCode != 200 {
|
|
return nil, fmt.Errorf("Unexpected response code (%d). %d expected. Details: \"%s\"", response.StatusCode, 200, body)
|
|
}
|
|
|
|
if errRead != nil {
|
|
return nil, errRead
|
|
}
|
|
|
|
return marshalResponse(body)
|
|
}
|
|
|
|
func marshalResponse(body []byte) (*malwareExecuteScanResponse, error) {
|
|
|
|
var scanResponse malwareExecuteScanResponse
|
|
|
|
err := json.Unmarshal(body, &scanResponse)
|
|
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, fmt.Sprintf("Unmarshalling of response body failed. Body: '%s'", body))
|
|
}
|
|
|
|
return &scanResponse, nil
|
|
}
|
|
|
|
func validateHash(remoteHash, fileName string) error {
|
|
|
|
hash, err := getSHA256(fileName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if hash == remoteHash {
|
|
log.Entry().Infof("Hash returned from malwarescan service matches file hash for file '%s' (%s)", fileName, hash)
|
|
} else {
|
|
return fmt.Errorf("Hash returned from malwarescan service ('%s') does not match file hash ('%s') for file '%s'",
|
|
remoteHash, hash, fileName)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func _getSHA256(fileName string) (string, error) {
|
|
|
|
f, err := open(fileName)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer f.Close()
|
|
|
|
hash := sha256.New()
|
|
_, err = io.Copy(hash, f)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return fmt.Sprintf("%x", string(hash.Sum(nil))), nil
|
|
}
|
|
|
|
func prepareHeaders() http.Header {
|
|
headers := http.Header{}
|
|
headers.Add("Content-Type", "application/octet-stream")
|
|
return headers
|
|
}
|