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  }