github.com/SAP/jenkins-library@v1.362.0/cmd/malwareExecuteScan.go (about)

     1  package cmd
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	piperDocker "github.com/SAP/jenkins-library/pkg/docker"
     7  	piperhttp "github.com/SAP/jenkins-library/pkg/http"
     8  	"github.com/SAP/jenkins-library/pkg/log"
     9  	"github.com/SAP/jenkins-library/pkg/malwarescan"
    10  	"github.com/SAP/jenkins-library/pkg/piperutils"
    11  	"github.com/SAP/jenkins-library/pkg/telemetry"
    12  	"github.com/SAP/jenkins-library/pkg/toolrecord"
    13  	"github.com/pkg/errors"
    14  	"io"
    15  	"os"
    16  	"strings"
    17  	"time"
    18  )
    19  
    20  type malwareScanUtils interface {
    21  	OpenFile(name string, flag int, perm os.FileMode) (io.ReadCloser, error)
    22  	SHA256(path string) (string, error)
    23  
    24  	newDockerClient(piperDocker.ClientOptions) piperDocker.Download
    25  
    26  	malwarescan.Client
    27  	piperutils.FileUtils
    28  }
    29  
    30  type malwareScanUtilsBundle struct {
    31  	malwarescan.Client
    32  	*piperutils.Files
    33  }
    34  
    35  func (utils *malwareScanUtilsBundle) OpenFile(name string, flag int, perm os.FileMode) (io.ReadCloser, error) {
    36  	return utils.Files.FileOpen(name, flag, perm)
    37  }
    38  
    39  func (utils *malwareScanUtilsBundle) newDockerClient(options piperDocker.ClientOptions) piperDocker.Download {
    40  	dClient := piperDocker.Client{}
    41  	dClient.SetOptions(options)
    42  	return &dClient
    43  }
    44  
    45  func newMalwareScanUtilsBundle(config malwareExecuteScanOptions) *malwareScanUtilsBundle {
    46  	timeout, err := time.ParseDuration(fmt.Sprintf("%ss", config.Timeout))
    47  	if err != nil {
    48  		timeout = 60
    49  		log.Entry().Warnf("Unable to parse timeout for malwareScan: '%v'. Falling back to %ds", err, timeout)
    50  	}
    51  
    52  	httpClientOptions := piperhttp.ClientOptions{
    53  		Username:           config.Username,
    54  		Password:           config.Password,
    55  		MaxRequestDuration: timeout,
    56  		TransportTimeout:   timeout,
    57  	}
    58  
    59  	httpClient := &piperhttp.Client{}
    60  	httpClient.SetOptions(httpClientOptions)
    61  
    62  	return &malwareScanUtilsBundle{
    63  		Client: &malwarescan.ClientImpl{
    64  			HTTPClient: httpClient,
    65  			Host:       config.Host,
    66  		},
    67  		Files: &piperutils.Files{},
    68  	}
    69  }
    70  
    71  func malwareExecuteScan(config malwareExecuteScanOptions, telemetryData *telemetry.CustomData) {
    72  	utils := newMalwareScanUtilsBundle(config)
    73  
    74  	err := runMalwareScan(&config, telemetryData, utils)
    75  	if err != nil {
    76  		log.Entry().WithError(err).Fatal("step execution failed")
    77  	}
    78  }
    79  
    80  func runMalwareScan(config *malwareExecuteScanOptions, telemetryData *telemetry.CustomData, utils malwareScanUtils) error {
    81  	file, err := selectAndPrepareFileForMalwareScan(config, utils)
    82  	if err != nil {
    83  		return err
    84  	}
    85  
    86  	log.Entry().Infof("Scanning file \"%s\" for malware using service \"%s\"", file, config.Host)
    87  
    88  	candidate, err := utils.OpenFile(file, os.O_RDONLY, 0666)
    89  	if err != nil {
    90  		return err
    91  	}
    92  	defer candidate.Close()
    93  
    94  	scannerInfo, err := utils.Info()
    95  
    96  	log.Entry().Infof("***************************************")
    97  	log.Entry().Infof("* Engine:     %s", scannerInfo.EngineVersion)
    98  	log.Entry().Infof("* Signatures: %s", scannerInfo.SignatureTimestamp)
    99  	log.Entry().Infof("***************************************")
   100  
   101  	if _, err = createToolRecordMalwareScan(utils, "./", config, scannerInfo); err != nil {
   102  		return err
   103  	}
   104  
   105  	scanResponse, err := utils.Scan(candidate)
   106  
   107  	if err != nil {
   108  		return err
   109  	}
   110  
   111  	if err = createMalwareScanReport(config, scanResponse, utils); err != nil {
   112  		return err
   113  	}
   114  
   115  	log.Entry().Debugf(
   116  		"File '%s' has been scanned. MalwareDetected: %t, EncryptedContentDetected: %t, ScanSize: %d, MimeType: '%s', SHA256: '%s', Finding: '%s'",
   117  		file,
   118  		scanResponse.MalwareDetected,
   119  		scanResponse.EncryptedContentDetected,
   120  		scanResponse.ScanSize,
   121  		scanResponse.MimeType,
   122  		scanResponse.SHA256,
   123  		scanResponse.Finding)
   124  
   125  	if err = validateHash(scanResponse.SHA256, file, utils); err != nil {
   126  		return err
   127  	}
   128  
   129  	if scanResponse.MalwareDetected || scanResponse.EncryptedContentDetected {
   130  		return fmt.Errorf("Malware scan failed for file '%s'. Malware detected: %t, encrypted content detected: %t, finding: %v",
   131  			file, scanResponse.MalwareDetected, scanResponse.EncryptedContentDetected, scanResponse.Finding)
   132  	}
   133  
   134  	log.Entry().Infof("Malware scan succeeded for file '%s'. Malware detected: %t, encrypted content detected: %t",
   135  		file, scanResponse.MalwareDetected, scanResponse.EncryptedContentDetected)
   136  
   137  	return nil
   138  }
   139  
   140  func selectAndPrepareFileForMalwareScan(config *malwareExecuteScanOptions, utils malwareScanUtils) (string, error) {
   141  	if len(config.ScanFile) > 0 {
   142  		return config.ScanFile, nil
   143  	}
   144  
   145  	// automatically detect the file to be scanned depending on the buildtool
   146  	if len(config.ScanImage) > 0 {
   147  		saveImageOptions := containerSaveImageOptions{
   148  			ContainerImage:            config.ScanImage,
   149  			ContainerRegistryURL:      config.ScanImageRegistryURL,
   150  			ContainerRegistryUser:     config.ContainerRegistryUser,
   151  			ContainerRegistryPassword: config.ContainerRegistryPassword,
   152  			DockerConfigJSON:          config.DockerConfigJSON,
   153  			ImageFormat:               "tarball",
   154  		}
   155  
   156  		dClientOptions := piperDocker.ClientOptions{ImageName: saveImageOptions.ContainerImage, RegistryURL: saveImageOptions.ContainerRegistryURL, LocalPath: "", ImageFormat: saveImageOptions.ImageFormat}
   157  		dClient := utils.newDockerClient(dClientOptions)
   158  
   159  		tarFile, err := runContainerSaveImage(&saveImageOptions, &telemetry.CustomData{}, "./cache", "", dClient, utils)
   160  
   161  		if err != nil {
   162  			if strings.Contains(fmt.Sprint(err), "no image found") {
   163  				log.SetErrorCategory(log.ErrorConfiguration)
   164  			}
   165  			return "", errors.Wrapf(err, "failed to download Docker image %v", config.ScanImage)
   166  		}
   167  		return tarFile, nil
   168  	}
   169  
   170  	return "", fmt.Errorf("Please specify a file to be scanned")
   171  }
   172  
   173  func validateHash(remoteHash, fileName string, utils malwareScanUtils) error {
   174  	hash, err := utils.SHA256(fileName)
   175  	if err != nil {
   176  		return err
   177  	}
   178  
   179  	if hash == remoteHash {
   180  		log.Entry().Infof("Hash returned from malwarescan service matches file hash for file '%s' (%s)", fileName, hash)
   181  	} else {
   182  		return fmt.Errorf("Hash returned from malwarescan service ('%s') does not match file hash ('%s') for file '%s'",
   183  			remoteHash, hash, fileName)
   184  	}
   185  
   186  	return nil
   187  }
   188  
   189  // create toolrecord file for malwarescan
   190  func createToolRecordMalwareScan(utils malwareScanUtils, workspace string, config *malwareExecuteScanOptions, scanner *malwarescan.Info) (string, error) {
   191  	record := toolrecord.New(utils, workspace, "malwarescan", config.Host)
   192  	record.SetOverallDisplayData("Malware Scanner", "")
   193  
   194  	if err := record.AddKeyData("engineVersion", scanner.EngineVersion, "Engine Version", ""); err != nil {
   195  		return "", err
   196  	}
   197  
   198  	if err := record.AddKeyData("signatureTimestamp", scanner.SignatureTimestamp, "Signature Timestamp", ""); err != nil {
   199  		return "", err
   200  	}
   201  
   202  	if err := record.Persist(); err != nil {
   203  		return "", err
   204  	}
   205  
   206  	return record.GetFileName(), nil
   207  }
   208  
   209  func createMalwareScanReport(config *malwareExecuteScanOptions, scanResult *malwarescan.ScanResult, utils malwareScanUtils) error {
   210  	scanResultJSON, err := json.Marshal(scanResult)
   211  
   212  	if err != nil {
   213  		return err
   214  	}
   215  
   216  	return utils.FileWrite(config.ReportFileName, scanResultJSON, 0666)
   217  }