github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/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 }