mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-12-12 10:55:20 +02:00
a46f796bcd
* chore: cleanup reporting & some incorrect file usage in tests * cleanup interface * chore: remove comment * preserve error handling * Rename FileUtils.go to fileUtils.go * clean up formatting * chore: address static check findings * fix brittle test * chore: cleanup formatting
218 lines
6.7 KiB
Go
218 lines
6.7 KiB
Go
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(utils, "./", 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,
|
|
ImageFormat: "tarball",
|
|
}
|
|
|
|
dClientOptions := piperDocker.ClientOptions{ImageName: saveImageOptions.ContainerImage, RegistryURL: saveImageOptions.ContainerRegistryURL, LocalPath: "", ImageFormat: saveImageOptions.ImageFormat}
|
|
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(utils malwareScanUtils, workspace string, config *malwareExecuteScanOptions, scanner *malwarescan.Info) (string, error) {
|
|
record := toolrecord.New(utils, 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)
|
|
}
|