github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/cmd/credentialdiggerScan.go (about) 1 package cmd 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "strconv" 8 9 "github.com/SAP/jenkins-library/pkg/command" 10 piperhttp "github.com/SAP/jenkins-library/pkg/http" 11 "github.com/SAP/jenkins-library/pkg/log" 12 "github.com/SAP/jenkins-library/pkg/orchestrator" 13 "github.com/SAP/jenkins-library/pkg/piperutils" 14 "github.com/SAP/jenkins-library/pkg/telemetry" 15 "github.com/pkg/errors" 16 ) 17 18 const piperDbName string = "piper_step_db.db" 19 const piperReportName string = "findings.csv" 20 21 type credentialdiggerUtils interface { 22 command.ExecRunner 23 piperutils.FileUtils 24 } 25 26 type credentialdiggerUtilsBundle struct { 27 *command.Command 28 *piperutils.Files 29 } 30 31 func newCDUtils() credentialdiggerUtils { 32 utils := credentialdiggerUtilsBundle{ 33 Command: &command.Command{}, 34 Files: &piperutils.Files{}, 35 } 36 // Reroute command output to logging framework 37 utils.Stdout(log.Writer()) 38 utils.Stderr(log.Writer()) 39 return &utils 40 } 41 42 func credentialdiggerScan(config credentialdiggerScanOptions, telemetryData *telemetry.CustomData) error { 43 utils := newCDUtils() 44 // 0: Get attributes from orchestrator 45 provider, prov_err := orchestrator.NewOrchestratorSpecificConfigProvider() 46 if prov_err != nil { 47 log.Entry().WithError(prov_err).Error( 48 "credentialdiggerScan: unable to load orchestrator specific configuration.") 49 } 50 if config.Repository == "" { 51 // Get current repository from orchestrator 52 repoUrlOrchestrator := provider.GetRepoURL() 53 if repoUrlOrchestrator == "n/a" { 54 // Jenkins configuration error 55 log.Entry().WithError(errors.New( 56 fmt.Sprintf("Unknown repository URL %s", repoUrlOrchestrator))).Error( 57 "Repository URL n/a. Please verify git plugin is installed.") 58 } 59 config.Repository = repoUrlOrchestrator 60 log.Entry().Debug("Use current repository: ", repoUrlOrchestrator) 61 } 62 if provider.IsPullRequest() { 63 // set the pr number 64 config.PrNumber, _ = strconv.Atoi(provider.GetPullRequestConfig().Key) 65 log.Entry().Debug("Scan the current pull request: number ", config.PrNumber) 66 } 67 68 // 1: Add rules 69 log.Entry().Info("Load rules") 70 err := credentialdiggerAddRules(&config, telemetryData, utils) 71 if err != nil { 72 log.Entry().Error("credentialdiggerScan: Failed running credentialdigger add_rules") 73 return err 74 } 75 log.Entry().Info("Rules added") 76 77 // 2: Scan the repository 78 // Choose between scan-pr, scan-snapshot, and full-scan (with this priority 79 // order) 80 switch { 81 case config.PrNumber != 0: // int type is not nillable in golang 82 log.Entry().Debug("Scan PR") 83 // if a PrNumber is declared, run scan_pr 84 err = credentialdiggerScanPR(&config, telemetryData, utils) // scan PR with CD 85 case config.Snapshot != "": 86 log.Entry().Debug("Scan snapshot") 87 // if a Snapshot is declared, run scan_snapshot 88 err = credentialdiggerScanSnapshot(&config, telemetryData, utils) // scan Snapshot with CD 89 default: 90 // The default case is the normal full scan 91 log.Entry().Debug("Full scan repo") 92 err = credentialdiggerFullScan(&config, telemetryData, utils) // full scan with CD 93 } 94 // err is an error exit number when there are findings 95 if err == nil { 96 log.Entry().Info("No discoveries found in this repo") 97 // If there are no findings, there is no need to export an empty report 98 return nil 99 } 100 101 // 3: Get discoveries 102 err = credentialdiggerGetDiscoveries(&config, telemetryData, utils) 103 if err != nil { 104 // The exit number is the number of discoveries 105 // Therefore, this error is not relevant, if raised 106 log.Entry().Warn("There are findings to review") 107 } 108 109 // 4: Export report in workspace 110 reports := []piperutils.Path{} 111 reports = append(reports, piperutils.Path{Target: fmt.Sprintf("%v", piperReportName)}) 112 piperutils.PersistReportsAndLinks("credentialdiggerScan", "./", utils, reports, nil) 113 114 return nil 115 } 116 117 func executeCredentialDiggerProcess(utils credentialdiggerUtils, args []string) error { 118 return utils.RunExecutable("credentialdigger", args...) 119 } 120 121 // hasConfigurationFile checks if the given file exists 122 func hasRulesFile(file string, utils credentialdiggerUtils) bool { 123 exists, err := utils.FileExists(file) 124 if err != nil { 125 log.Entry().WithError(err).Error() 126 } 127 return exists 128 } 129 130 func credentialdiggerAddRules(config *credentialdiggerScanOptions, telemetryData *telemetry.CustomData, service credentialdiggerUtils) error { 131 // Credentialdigger home can be changed with local forks (e.g., for local piper runs) 132 cdHome := "/credential-digger-ui" // cdHome path as in docker container 133 if cdh := os.Getenv("CREDENTIALDIGGER_HOME"); cdh != "" { 134 cdHome = cdh 135 } 136 log.Entry().Debug("Use credentialdigger home ", cdHome) 137 // Set the rule file to the standard ruleset shipped within credential 138 // digger container 139 ruleFile := filepath.Join(cdHome, "backend", "rules.yml") 140 141 if config.RulesDownloadURL != "" { 142 // Download custom rule file from this URL 143 log.Entry().Debugf("Download custom ruleset from %v", config.RulesDownloadURL) 144 dlClient := piperhttp.Client{} 145 ruleFile := filepath.Join(cdHome, "backend", "custom-rules.yml") 146 dlClient.DownloadFile(config.RulesDownloadURL, ruleFile, nil, nil) 147 log.Entry().Info("Download and use remote rules") 148 } else { 149 log.Entry().Debug("Use a local ruleset") 150 // Use rules defined in stashed file 151 if hasRulesFile(config.RulesFile, service) { 152 log.Entry().WithField("file", config.RulesFile).Info("Use stashed rules file from repository") 153 ruleFile = config.RulesFile 154 } else { 155 log.Entry().Info("Use standard pre-defined rules") 156 } 157 158 } 159 cmd_list := []string{"add_rules", "--sqlite", piperDbName, ruleFile} 160 return executeCredentialDiggerProcess(service, cmd_list) 161 } 162 163 func credentialdiggerGetDiscoveries(config *credentialdiggerScanOptions, telemetryData *telemetry.CustomData, service credentialdiggerUtils) error { 164 log.Entry().Info("Get discoveries") 165 cmd_list := []string{"get_discoveries", config.Repository, "--sqlite", piperDbName, 166 "--save", piperReportName} 167 // Export all the discoveries or export only new ones 168 if !config.ExportAll { 169 cmd_list = append(cmd_list, "--state", "new") 170 } 171 err := executeCredentialDiggerProcess(service, cmd_list) 172 if err != nil { 173 log.Entry().Error("credentialdiggerScan: Failed running credentialdigger get_discoveries") 174 log.Entry().Error(err) 175 return err 176 } 177 log.Entry().Info("Scan complete") 178 return nil 179 } 180 181 func credentialdiggerBuildCommonArgs(config *credentialdiggerScanOptions) []string { 182 /*Some arguments are the same for all the scan flavors. Build them here 183 * not to duplicate code.*/ 184 scan_args := []string{} 185 // Repository url and sqlite db (always mandatory) 186 scan_args = append(scan_args, config.Repository, "--sqlite", piperDbName) 187 //git token is not mandatory for base credential digger tool, but in 188 //piper it is 189 scan_args = append(scan_args, "--git_token", config.Token) 190 //debug 191 if config.Debug { 192 log.Entry().Debug("Run the scan in debug mode") 193 scan_args = append(scan_args, "--debug") 194 } 195 //models 196 if len(config.Models) > 0 { 197 log.Entry().Debugf("Enable models %v", config.Models) 198 scan_args = append(scan_args, "--models") 199 scan_args = append(scan_args, config.Models...) 200 } 201 202 return scan_args 203 } 204 205 func credentialdiggerScanSnapshot(config *credentialdiggerScanOptions, telemetryData *telemetry.CustomData, service credentialdiggerUtils) error { 206 log.Entry().Infof("Scan Snapshot %v from repo %v", config.Snapshot, config.Repository) 207 cmd_list := []string{"scan_snapshot", 208 "--snapshot", config.Snapshot} 209 cmd_list = append(cmd_list, credentialdiggerBuildCommonArgs(config)...) 210 leaks := executeCredentialDiggerProcess(service, cmd_list) 211 if leaks != nil { 212 log.Entry().Warn("The scan found potential leaks in this Snapshot") 213 return leaks 214 } else { 215 log.Entry().Info("No leaks found") 216 return nil 217 } 218 } 219 220 func credentialdiggerScanPR(config *credentialdiggerScanOptions, telemetryData *telemetry.CustomData, service credentialdiggerUtils) error { 221 log.Entry().Infof("Scan PR %v from repo %v", config.PrNumber, config.Repository) 222 cmd_list := []string{"scan_pr", 223 "--pr", strconv.Itoa(config.PrNumber), 224 "--api_endpoint", config.APIURL} 225 cmd_list = append(cmd_list, credentialdiggerBuildCommonArgs(config)...) 226 leaks := executeCredentialDiggerProcess(service, cmd_list) 227 if leaks != nil { 228 log.Entry().Warn("The scan found potential leaks in this PR") 229 return leaks 230 } else { 231 log.Entry().Info("No leaks found") 232 return nil 233 } 234 } 235 236 func credentialdiggerFullScan(config *credentialdiggerScanOptions, telemetryData *telemetry.CustomData, service credentialdiggerUtils) error { 237 log.Entry().Infof("Full scan of repository %v", config.Repository) 238 cmd_list := []string{"scan"} 239 cmd_list = append(cmd_list, credentialdiggerBuildCommonArgs(config)...) 240 leaks := executeCredentialDiggerProcess(service, cmd_list) 241 if leaks != nil { 242 log.Entry().Warn("The scan found potential leaks") 243 log.Entry().Warnf("%v potential leaks found", leaks) 244 return leaks 245 } else { 246 log.Entry().Info("No leaks found") 247 return nil 248 } 249 }