1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-12-14 11:03:09 +02:00
sap-jenkins-library/cmd/malwareExecuteScan.go

217 lines
6.6 KiB
Go
Raw Normal View History

package cmd
import (
"encoding/json"
"fmt"
piperDocker "github.com/SAP/jenkins-library/pkg/docker"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/malwarescan"
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/SAP/jenkins-library/pkg/toolrecord"
"github.com/pkg/errors"
"io"
"os"
"strings"
"time"
)
type malwareScanUtils interface {
OpenFile(name string, flag int, perm os.FileMode) (io.ReadCloser, error)
SHA256(path string) (string, error)
newDockerClient(piperDocker.ClientOptions) piperDocker.Download
malwarescan.Client
piperutils.FileUtils
}
type malwareScanUtilsBundle struct {
malwarescan.Client
*piperutils.Files
}
func (utils *malwareScanUtilsBundle) OpenFile(name string, flag int, perm os.FileMode) (io.ReadCloser, error) {
return utils.Files.FileOpen(name, flag, perm)
}
func (utils *malwareScanUtilsBundle) newDockerClient(options piperDocker.ClientOptions) piperDocker.Download {
dClient := piperDocker.Client{}
dClient.SetOptions(options)
return &dClient
}
func newMalwareScanUtilsBundle(config malwareExecuteScanOptions) *malwareScanUtilsBundle {
timeout, err := time.ParseDuration(fmt.Sprintf("%ss", config.Timeout))
if err != nil {
timeout = 60
log.Entry().Warnf("Unable to parse timeout for malwareScan: '%v'. Falling back to %ds", err, timeout)
}
httpClientOptions := piperhttp.ClientOptions{
Username: config.Username,
Password: config.Password,
MaxRequestDuration: timeout,
TransportTimeout: timeout,
}
httpClient := &piperhttp.Client{}
httpClient.SetOptions(httpClientOptions)
return &malwareScanUtilsBundle{
Client: &malwarescan.ClientImpl{
HTTPClient: httpClient,
Host: config.Host,
},
Files: &piperutils.Files{},
}
}
func malwareExecuteScan(config malwareExecuteScanOptions, telemetryData *telemetry.CustomData) {
utils := newMalwareScanUtilsBundle(config)
err := runMalwareScan(&config, telemetryData, utils)
if err != nil {
log.Entry().WithError(err).Fatal("step execution failed")
}
}
func runMalwareScan(config *malwareExecuteScanOptions, telemetryData *telemetry.CustomData, utils malwareScanUtils) error {
file, err := selectAndPrepareFileForMalwareScan(config, utils)
if err != nil {
return err
}
log.Entry().Infof("Scanning file \"%s\" for malware using service \"%s\"", file, config.Host)
candidate, err := utils.OpenFile(file, os.O_RDONLY, 0666)
if err != nil {
return err
}
defer candidate.Close()
scannerInfo, err := utils.Info()
log.Entry().Infof("***************************************")
log.Entry().Infof("* Engine: %s", scannerInfo.EngineVersion)
log.Entry().Infof("* Signatures: %s", scannerInfo.SignatureTimestamp)
log.Entry().Infof("***************************************")
if _, err = createToolRecordMalwareScan("./", config, scannerInfo); err != nil {
return err
}
scanResponse, err := utils.Scan(candidate)
if err != nil {
return err
}
if err = createMalwareScanReport(config, scanResponse, utils); err != nil {
return err
}
log.Entry().Debugf(
"File '%s' has been scanned. MalwareDetected: %t, EncryptedContentDetected: %t, ScanSize: %d, MimeType: '%s', SHA256: '%s', Finding: '%s'",
file,
scanResponse.MalwareDetected,
scanResponse.EncryptedContentDetected,
scanResponse.ScanSize,
scanResponse.MimeType,
scanResponse.SHA256,
scanResponse.Finding)
if err = validateHash(scanResponse.SHA256, file, utils); 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, finding: %v",
file, scanResponse.MalwareDetected, scanResponse.EncryptedContentDetected, scanResponse.Finding)
}
log.Entry().Infof("Malware scan succeeded for file '%s'. Malware detected: %t, encrypted content detected: %t",
file, scanResponse.MalwareDetected, scanResponse.EncryptedContentDetected)
return nil
}
func selectAndPrepareFileForMalwareScan(config *malwareExecuteScanOptions, utils malwareScanUtils) (string, error) {
if len(config.ScanFile) > 0 {
return config.ScanFile, nil
}
// automatically detect the file to be scanned depending on the buildtool
if len(config.ScanImage) > 0 {
saveImageOptions := containerSaveImageOptions{
ContainerImage: config.ScanImage,
ContainerRegistryURL: config.ScanImageRegistryURL,
ContainerRegistryUser: config.ContainerRegistryUser,
ContainerRegistryPassword: config.ContainerRegistryPassword,
DockerConfigJSON: config.DockerConfigJSON,
}
dClientOptions := piperDocker.ClientOptions{ImageName: saveImageOptions.ContainerImage, RegistryURL: saveImageOptions.ContainerRegistryURL, LocalPath: ""}
dClient := utils.newDockerClient(dClientOptions)
tarFile, err := runContainerSaveImage(&saveImageOptions, &telemetry.CustomData{}, "./cache", "", dClient, utils)
if err != nil {
if strings.Contains(fmt.Sprint(err), "no image found") {
log.SetErrorCategory(log.ErrorConfiguration)
}
return "", errors.Wrapf(err, "failed to download Docker image %v", config.ScanImage)
}
return tarFile, nil
}
return "", fmt.Errorf("Please specify a file to be scanned")
}
func validateHash(remoteHash, fileName string, utils malwareScanUtils) error {
hash, err := utils.SHA256(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
}
// create toolrecord file for malwarescan
func createToolRecordMalwareScan(workspace string, config *malwareExecuteScanOptions, scanner *malwarescan.Info) (string, error) {
record := toolrecord.New(workspace, "malwarescan", config.Host)
record.SetOverallDisplayData("Malware Scanner", "")
if err := record.AddKeyData("engineVersion", scanner.EngineVersion, "Engine Version", ""); err != nil {
return "", err
}
if err := record.AddKeyData("signatureTimestamp", scanner.SignatureTimestamp, "Signature Timestamp", ""); err != nil {
return "", err
}
if err := record.Persist(); err != nil {
return "", err
}
return record.GetFileName(), nil
}
func createMalwareScanReport(config *malwareExecuteScanOptions, scanResult *malwarescan.ScanResult, utils malwareScanUtils) error {
scanResultJSON, err := json.Marshal(scanResult)
if err != nil {
return err
}
return utils.FileWrite(config.ReportFileName, scanResultJSON, 0666)
}