github.com/projectdiscovery/nuclei/v2@v2.9.15/internal/runner/cloud.go (about) 1 package runner 2 3 import ( 4 "io" 5 "io/fs" 6 "os" 7 "path" 8 "path/filepath" 9 "strconv" 10 "strings" 11 12 jsoniter "github.com/json-iterator/go" 13 "github.com/olekukonko/tablewriter" 14 "github.com/pkg/errors" 15 "github.com/projectdiscovery/gologger" 16 "github.com/projectdiscovery/nuclei/v2/internal/runner/nucleicloud" 17 "github.com/projectdiscovery/nuclei/v2/pkg/output" 18 "github.com/projectdiscovery/nuclei/v2/pkg/templates/extensions" 19 ) 20 21 // Get all the scan lists for a user/apikey. 22 func (r *Runner) getScanList(limit int) error { 23 lastTime := "2099-01-02 15:04:05 +0000 UTC" 24 header := []string{"ID", "Timestamp", "Targets", "Templates", "Matched", "Duration", "Status"} 25 26 var ( 27 values [][]string 28 count int 29 ) 30 for { 31 items, err := r.cloudClient.GetScans(limit, lastTime) 32 if err != nil { 33 return err 34 } 35 if len(items) == 0 { 36 break 37 } 38 for _, v := range items { 39 count++ 40 lastTime = v.CreatedAt.String() 41 res := nucleicloud.PrepareScanListOutput(v) 42 if r.options.JSONL { 43 _ = jsoniter.NewEncoder(os.Stdout).Encode(res) 44 } else if !r.options.NoTables { 45 values = append(values, []string{strconv.FormatInt(res.ScanID, 10), res.Timestamp, strconv.Itoa(res.Target), strconv.Itoa(res.Template), strconv.Itoa(res.ScanResult), res.ScanTime, res.ScanStatus}) 46 } else { 47 gologger.Silent().Msgf("%d. [%s] [TARGETS: %d] [TEMPLATES: %d] [MATCHED: %d] [DURATION: %s] [STATUS: %s]\n", res.ScanID, res.Timestamp, res.Target, res.Template, res.ScanResult, res.ScanTime, strings.ToUpper(res.ScanStatus)) 48 49 } 50 } 51 } 52 if count == 0 { 53 return errors.New("no scan found") 54 } 55 if !r.options.NoTables { 56 r.prettyPrintTable(header, values) 57 } 58 return nil 59 } 60 61 func (r *Runner) listDatasources() error { 62 datasources, err := r.cloudClient.ListDatasources() 63 if err != nil { 64 return err 65 } 66 if len(datasources) == 0 { 67 return errors.New("no cloud datasource found") 68 } 69 70 header := []string{"ID", "UpdatedAt", "Type", "Repo", "Path"} 71 var values [][]string 72 for _, source := range datasources { 73 if r.options.JSONL { 74 _ = jsoniter.NewEncoder(os.Stdout).Encode(source) 75 } else if !r.options.NoTables { 76 values = append(values, []string{strconv.FormatInt(source.ID, 10), source.Updatedat.Format(nucleicloud.DDMMYYYYhhmmss), source.Type, source.Repo, source.Path}) 77 } else { 78 gologger.Silent().Msgf("%d. [%s] [%s] [%s] %s", source.ID, source.Updatedat.Format(nucleicloud.DDMMYYYYhhmmss), source.Type, source.Repo, source.Path) 79 } 80 } 81 if !r.options.NoTables { 82 r.prettyPrintTable(header, values) 83 } 84 return nil 85 } 86 87 func (r *Runner) listReportingSources() error { 88 items, err := r.cloudClient.ListReportingSources() 89 if err != nil { 90 return err 91 } 92 if len(items) == 0 { 93 return errors.New("no reporting source found") 94 } 95 96 header := []string{"ID", "Type", "ProjectName", "Enabled"} 97 var values [][]string 98 for _, source := range items { 99 if r.options.JSONL { 100 _ = jsoniter.NewEncoder(os.Stdout).Encode(source) 101 } else if !r.options.NoTables { 102 values = append(values, []string{strconv.FormatInt(source.ID, 10), source.Type, source.ProjectName, strconv.FormatBool(source.Enabled)}) 103 } else { 104 gologger.Silent().Msgf("%d. [%s] [%s] [%t]", source.ID, source.Type, source.ProjectName, source.Enabled) 105 } 106 } 107 108 if !r.options.NoTables { 109 r.prettyPrintTable(header, values) 110 } 111 return nil 112 } 113 114 func (r *Runner) listTargets() error { 115 items, err := r.cloudClient.ListTargets("") 116 if err != nil { 117 return err 118 } 119 if len(items) == 0 { 120 return errors.New("no target found") 121 } 122 123 header := []string{"ID", "Reference", "Count"} 124 var values [][]string 125 for _, source := range items { 126 if r.options.JSONL { 127 _ = jsoniter.NewEncoder(os.Stdout).Encode(source) 128 } else if !r.options.NoTables { 129 values = append(values, []string{strconv.FormatInt(source.ID, 10), source.Reference, strconv.FormatInt(source.Count, 10)}) 130 } else { 131 gologger.Silent().Msgf("%d. %s (%d)", source.ID, source.Reference, source.Count) 132 } 133 } 134 if !r.options.NoTables { 135 r.prettyPrintTable(header, values) 136 } 137 return nil 138 } 139 140 func (r *Runner) listTemplates() error { 141 items, err := r.cloudClient.ListTemplates("") 142 if err != nil { 143 return err 144 } 145 if len(items) == 0 { 146 return errors.New("no template found") 147 } 148 149 header := []string{"ID", "Reference"} 150 var values [][]string 151 for _, source := range items { 152 if r.options.JSONL { 153 _ = jsoniter.NewEncoder(os.Stdout).Encode(source) 154 } else if !r.options.NoTables { 155 values = append(values, []string{strconv.FormatInt(source.ID, 10), source.Reference}) 156 } else { 157 gologger.Silent().Msgf("%d. %s", source.ID, source.Reference) 158 } 159 } 160 if !r.options.NoTables { 161 r.prettyPrintTable(header, values) 162 } 163 return nil 164 } 165 166 func (r *Runner) prettyPrintTable(header []string, values [][]string) { 167 writer := tablewriter.NewWriter(os.Stdout) 168 writer.SetHeader(header) 169 writer.AppendBulk(values) 170 writer.Render() 171 } 172 173 func (r *Runner) deleteScan(id string) error { 174 ID, parseErr := strconv.ParseInt(id, 10, 64) 175 if parseErr != nil { 176 return errors.Wrap(parseErr, "could not parse scan id") 177 } 178 deleted, err := r.cloudClient.DeleteScan(ID) 179 if err != nil { 180 return errors.Wrap(err, "could not delete scan") 181 } 182 if !deleted.OK { 183 gologger.Error().Msgf("Error in deleting the scan %s.", id) 184 } else { 185 gologger.Info().Msgf("Scan deleted %s.", id) 186 } 187 return nil 188 } 189 190 func (r *Runner) getResults(id string, limit int) error { 191 ID, _ := strconv.ParseInt(id, 10, 64) 192 err := r.cloudClient.GetResults(ID, false, limit, func(re *output.ResultEvent) { 193 if outputErr := r.output.Write(re); outputErr != nil { 194 gologger.Warning().Msgf("Could not write output: %s", outputErr) 195 } 196 }) 197 return err 198 } 199 200 func (r *Runner) getTarget(id string) error { 201 var name string 202 ID, parseErr := strconv.ParseInt(id, 10, 64) 203 if parseErr != nil { 204 name = id 205 } 206 207 reader, err := r.cloudClient.GetTarget(ID, name) 208 if err != nil { 209 return errors.Wrap(err, "could not get target") 210 } 211 defer reader.Close() 212 213 _, _ = io.Copy(os.Stdout, reader) 214 return nil 215 } 216 217 func (r *Runner) getTemplate(id string) error { 218 var name string 219 ID, parseErr := strconv.ParseInt(id, 10, 64) 220 if parseErr != nil { 221 name = id 222 } 223 224 reader, err := r.cloudClient.GetTemplate(ID, name) 225 if err != nil { 226 return errors.Wrap(err, "could not get template") 227 } 228 defer reader.Close() 229 230 _, _ = io.Copy(os.Stdout, reader) 231 return nil 232 } 233 234 func (r *Runner) removeDatasource(datasource string) error { 235 var source string 236 ID, parseErr := strconv.ParseInt(datasource, 10, 64) 237 if parseErr != nil { 238 source = datasource 239 } 240 241 err := r.cloudClient.RemoveDatasource(ID, source) 242 if err == nil { 243 gologger.Info().Msgf("Datasource deleted %s", datasource) 244 } 245 return err 246 } 247 248 func (r *Runner) toggleReportingSource(source string, status bool) error { 249 ID, parseErr := strconv.ParseInt(source, 10, 64) 250 if parseErr != nil { 251 return errors.Wrap(parseErr, "could not parse reporting source id") 252 } 253 254 err := r.cloudClient.ToggleReportingSource(ID, status) 255 if err == nil { 256 t := "enabled" 257 if !status { 258 t = "disabled" 259 } 260 gologger.Info().Msgf("Reporting source %s %s", t, source) 261 } 262 return err 263 } 264 265 func (r *Runner) addTemplate(location string) error { 266 walkErr := filepath.WalkDir(location, func(path string, d fs.DirEntry, err error) error { 267 if err != nil { 268 return err 269 } 270 if d.IsDir() || !strings.EqualFold(filepath.Ext(path), extensions.YAML) { 271 return nil 272 } 273 base := filepath.Base(path) 274 reference, templateErr := r.cloudClient.AddTemplate(base, path) 275 if templateErr != nil { 276 gologger.Error().Msgf("Could not upload %s: %s", path, templateErr) 277 } else if reference != "" { 278 gologger.Info().Msgf("Uploaded template %s: %s", base, reference) 279 } 280 return nil 281 }) 282 return walkErr 283 } 284 285 func (r *Runner) addTarget(location string) error { 286 walkErr := filepath.WalkDir(location, func(path string, d fs.DirEntry, err error) error { 287 if err != nil { 288 return err 289 } 290 if d.IsDir() || !strings.EqualFold(filepath.Ext(path), ".txt") { 291 return nil 292 } 293 base := filepath.Base(path) 294 reference, targetErr := r.cloudClient.AddTarget(base, path) 295 if targetErr != nil { 296 gologger.Error().Msgf("Could not upload %s: %s", location, targetErr) 297 } else if reference != "" { 298 gologger.Info().Msgf("Uploaded target %s: %s", base, reference) 299 } 300 return nil 301 }) 302 return walkErr 303 } 304 305 func (r *Runner) removeTarget(item string) error { 306 var err error 307 if ID, parseErr := strconv.ParseInt(item, 10, 64); parseErr == nil { 308 err = r.cloudClient.RemoveTarget(ID, "") 309 } else if strings.EqualFold(path.Ext(item), ".txt") { 310 err = r.cloudClient.RemoveTarget(0, item) 311 } else { 312 return r.removeTargetPrefix(item) 313 } 314 if err != nil { 315 gologger.Error().Msgf("Error in deleting target %s: %s", item, err) 316 } else { 317 gologger.Info().Msgf("Target deleted %s", item) 318 } 319 return nil 320 } 321 322 func (r *Runner) removeTargetPrefix(item string) error { 323 response, err := r.cloudClient.ListTargets(item) 324 if err != nil { 325 return errors.Wrap(err, "could not list targets") 326 } 327 for _, item := range response { 328 if err := r.cloudClient.RemoveTarget(item.ID, ""); err != nil { 329 gologger.Error().Msgf("Error in deleting target %s: %s", item.Reference, err) 330 } else { 331 gologger.Info().Msgf("Target deleted %s", item.Reference) 332 } 333 } 334 return nil 335 } 336 337 func (r *Runner) removeTemplate(item string) error { 338 var err error 339 if ID, parseErr := strconv.ParseInt(item, 10, 64); parseErr == nil { 340 err = r.cloudClient.RemoveTemplate(ID, "") 341 } else if strings.EqualFold(path.Ext(item), extensions.YAML) { 342 err = r.cloudClient.RemoveTemplate(0, item) 343 } else { 344 return r.removeTemplatePrefix(item) 345 } 346 if err != nil { 347 gologger.Error().Msgf("Error in deleting template %s: %s", item, err) 348 } else { 349 gologger.Info().Msgf("Template deleted %s", item) 350 } 351 return nil 352 } 353 354 func (r *Runner) removeTemplatePrefix(item string) error { 355 response, err := r.cloudClient.ListTemplates(item) 356 if err != nil { 357 return errors.Wrap(err, "could not list templates") 358 } 359 for _, item := range response { 360 if err := r.cloudClient.RemoveTemplate(item.ID, ""); err != nil { 361 gologger.Error().Msgf("Error in deleting template %s: %s", item.Reference, err) 362 } else { 363 gologger.Info().Msgf("Template deleted %s", item.Reference) 364 } 365 } 366 return nil 367 } 368 369 // initializeCloudDataSources initializes cloud data sources 370 func (r *Runner) addCloudDataSource(source string) error { 371 switch source { 372 case "s3": 373 token := strings.Join([]string{r.options.AwsAccessKey, r.options.AwsSecretKey, r.options.AwsRegion}, ":") 374 if _, err := r.processDataSourceItem(r.options.AwsBucketName, token, "s3"); err != nil { 375 return err 376 } 377 case "github": 378 for _, repo := range r.options.GitHubTemplateRepo { 379 if _, err := r.processDataSourceItem(repo, r.options.GitHubToken, "github"); err != nil { 380 return err 381 } 382 } 383 } 384 return nil 385 } 386 387 func (r *Runner) processDataSourceItem(repo, token, Type string) (int64, error) { 388 ID, err := r.cloudClient.StatusDataSource(nucleicloud.StatusDataSourceRequest{Repo: repo, Token: token}) 389 if err != nil { 390 if !strings.Contains(err.Error(), "no rows in result set") { 391 return 0, errors.Wrap(err, "could not get data source status") 392 } 393 394 gologger.Info().Msgf("Adding new data source + syncing: %s\n", repo) 395 resp, err := r.cloudClient.AddDataSource(nucleicloud.AddDataSourceRequest{Type: Type, Repo: repo, Token: token}) 396 if err != nil { 397 return 0, errors.Wrap(err, "could not add data source") 398 } 399 ID = resp.ID 400 if err = r.cloudClient.SyncDataSource(resp.ID); err != nil { 401 return 0, errors.Wrap(err, "could not sync data source") 402 } 403 if resp.Secret != "" { 404 gologger.Info().Msgf("Webhook URL for added source: %s/datasources/%s/webhook", r.options.CloudURL, resp.Hash) 405 gologger.Info().Msgf("Secret for webhook: %s", resp.Secret) 406 } 407 } 408 if r.options.UpdateTemplates { 409 gologger.Info().Msgf("Syncing data source: %s (%d)\n", repo, ID) 410 if err = r.cloudClient.SyncDataSource(ID); err != nil { 411 return 0, errors.Wrap(err, "could not sync data source") 412 } 413 } 414 return ID, nil 415 } 416 417 // addCloudReportingSource adds reporting sources to cloud 418 func (r *Runner) addCloudReportingSource() error { 419 rcOptions := r.issuesClient.GetReportingOptions() 420 if rcOptions == nil { 421 return nil 422 } 423 if rcOptions.Jira != nil { 424 payload, err := jsoniter.Marshal(rcOptions.Jira) 425 if err != nil { 426 return err 427 } 428 requestObj := nucleicloud.AddReportingSourceRequest{ 429 Type: "jira", 430 Payload: payload, 431 } 432 if _, err := r.cloudClient.AddReportingSource(requestObj); err != nil { 433 return errors.Wrap(err, "could not add reporting source") 434 } 435 gologger.Info().Msgf("Reporting source and webhook added for %s: %s", "jira", r.options.CloudURL) 436 } 437 return nil 438 }