1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-12-12 10:55:20 +02:00
sap-jenkins-library/cmd/malwareExecuteScan.go
Christopher Fenner b219fb6514
fix(typo): found by misspell (#2064)
* fix typos in step yamls

* fix typos in go files

* regenerate step code

* fix typos in md files

* fix typos in groovy files

* fix further typos
2020-09-24 07:41:06 +02:00

205 lines
5.6 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 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
// ourselves 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
}