manager.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. package manager
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "github.com/hako/durafmt"
  6. "github.com/mattn/go-colorable"
  7. log "github.com/sirupsen/logrus"
  8. "github.com/zricethezav/gitleaks/config"
  9. "github.com/zricethezav/gitleaks/options"
  10. "gopkg.in/src-d/go-git.v4"
  11. "os"
  12. "os/signal"
  13. "runtime"
  14. "sync"
  15. "text/tabwriter"
  16. "time"
  17. )
  18. // Manager is a struct containing options and configs as well CloneOptions and CloneDir.
  19. // This struct is passed into each NewRepo so we are not passing around the manager in func params.
  20. type Manager struct {
  21. Opts options.Options
  22. Config config.Config
  23. CloneOptions *git.CloneOptions
  24. CloneDir string
  25. leaks []Leak
  26. leakChan chan Leak
  27. leakWG *sync.WaitGroup
  28. stopChan chan os.Signal
  29. metadata Metadata
  30. metaWG *sync.WaitGroup
  31. }
  32. // Leak is a struct that contains information about some line of code that contains
  33. // sensitive information as determined by the rules set in a gitleaks config
  34. type Leak struct {
  35. Line string `json:"line"`
  36. Offender string `json:"offender"`
  37. Commit string `json:"commit"`
  38. Repo string `json:"repo"`
  39. Rule string `json:"rule"`
  40. Message string `json:"commitMessage"`
  41. Author string `json:"author"`
  42. Email string `json:"email"`
  43. File string `json:"file"`
  44. Date time.Time `json:"date"`
  45. Tags string `json:"tags"`
  46. }
  47. // AuditTime is a type used to determine total audit time
  48. type AuditTime int64
  49. // PatchTime is a type used to determine total patch time during an audit
  50. type PatchTime int64
  51. // CloneTime is a type used to determine total clone time
  52. type CloneTime int64
  53. // RegexTime is a type used to determine the time each rules' regex takes. This is especially useful
  54. // if you notice that gitleaks is taking a long time. You can use --debug to see the output of the regexTime
  55. // so you can determine which regex is not performing well.
  56. type RegexTime struct {
  57. Time int64
  58. Regex string
  59. }
  60. // Metadata is a struct used to communicate metadata about an audit like timings and total commit counts.
  61. type Metadata struct {
  62. mux sync.Mutex
  63. data map[string]interface{}
  64. timings chan interface{}
  65. RegexTime map[string]int64
  66. Commits int
  67. AuditTime int64
  68. patchTime int64
  69. cloneTime int64
  70. }
  71. func init() {
  72. log.SetOutput(os.Stdout)
  73. log.SetFormatter(&log.TextFormatter{
  74. ForceColors: true,
  75. FullTimestamp: true,
  76. })
  77. // Fix colors on Windows
  78. if runtime.GOOS == "windows" {
  79. log.SetOutput(colorable.NewColorableStdout())
  80. }
  81. }
  82. // GetLeaks returns all available leaks
  83. func (manager *Manager) GetLeaks() []Leak {
  84. // need to wait for any straggling leaks
  85. manager.leakWG.Wait()
  86. return manager.leaks
  87. }
  88. // SendLeaks accepts a leak and is used by the audit pkg. This is the public function
  89. // that allows other packages to send leaks to the manager.
  90. func (manager *Manager) SendLeaks(l Leak) {
  91. manager.leakWG.Add(1)
  92. manager.leakChan <- l
  93. }
  94. // receiveLeaks listens to leakChan for incoming leaks. If any are received, they are appended to the
  95. // manager's leaks for future reporting. If the -v/--verbose option is set the leaks will marshaled into
  96. // json and printed out.
  97. func (manager *Manager) receiveLeaks() {
  98. for leak := range manager.leakChan {
  99. manager.leaks = append(manager.leaks, leak)
  100. if manager.Opts.Verbose {
  101. var b []byte
  102. if manager.Opts.PrettyPrint {
  103. b, _ = json.MarshalIndent(leak, "", " ")
  104. } else {
  105. b, _ = json.Marshal(leak)
  106. }
  107. fmt.Println(string(b))
  108. }
  109. manager.leakWG.Done()
  110. }
  111. }
  112. // GetMetadata returns the metadata. TODO this may not need to be private
  113. func (manager *Manager) GetMetadata() Metadata {
  114. manager.metaWG.Wait()
  115. return manager.metadata
  116. }
  117. // receiveMetadata is where the messages sent to the metadata channel get consumed. You can view metadata
  118. // by running gitleaks with the --debug option set. This is extremely useful when trying to optimize regular
  119. // expressions as that what gitleaks spends most of its cycles on.
  120. func (manager *Manager) receiveMetadata() {
  121. for t := range manager.metadata.timings {
  122. switch ti := t.(type) {
  123. case CloneTime:
  124. manager.metadata.cloneTime += int64(ti)
  125. case AuditTime:
  126. manager.metadata.AuditTime += int64(ti)
  127. case PatchTime:
  128. manager.metadata.patchTime += int64(ti)
  129. case RegexTime:
  130. manager.metadata.RegexTime[ti.Regex] = manager.metadata.RegexTime[ti.Regex] + ti.Time
  131. }
  132. manager.metaWG.Done()
  133. }
  134. }
  135. // IncrementCommits increments total commits during an audit by i.
  136. func (manager *Manager) IncrementCommits(i int) {
  137. manager.metadata.mux.Lock()
  138. manager.metadata.Commits += i
  139. manager.metadata.mux.Unlock()
  140. }
  141. // RecordTime accepts an interface and sends it to the manager's time channel
  142. func (manager *Manager) RecordTime(t interface{}) {
  143. manager.metaWG.Add(1)
  144. manager.metadata.timings <- t
  145. }
  146. // NewManager accepts options and returns a manager struct. The manager is a container for gitleaks configurations,
  147. // options and channel receivers.
  148. func NewManager(opts options.Options, cfg config.Config) (*Manager, error) {
  149. cloneOpts, err := opts.CloneOptions()
  150. if err != nil {
  151. return nil, err
  152. }
  153. m := &Manager{
  154. Opts: opts,
  155. Config: cfg,
  156. CloneOptions: cloneOpts,
  157. stopChan: make(chan os.Signal, 1),
  158. leakChan: make(chan Leak),
  159. leakWG: &sync.WaitGroup{},
  160. metaWG: &sync.WaitGroup{},
  161. metadata: Metadata{
  162. RegexTime: make(map[string]int64),
  163. timings: make(chan interface{}),
  164. data: make(map[string]interface{}),
  165. },
  166. }
  167. signal.Notify(m.stopChan, os.Interrupt)
  168. // start receiving leaks and metadata
  169. go m.receiveLeaks()
  170. go m.receiveMetadata()
  171. go m.receiveInterrupt()
  172. return m, nil
  173. }
  174. // DebugOutput logs metadata and other messages that occurred during a gitleaks audit
  175. func (manager *Manager) DebugOutput() {
  176. log.Debugf("-------------------------\n")
  177. log.Debugf("| Times and Commit Counts|\n")
  178. log.Debugf("-------------------------\n")
  179. fmt.Println("totalAuditTime: ", durafmt.Parse(time.Duration(manager.metadata.AuditTime)*time.Nanosecond))
  180. fmt.Println("totalPatchTime: ", durafmt.Parse(time.Duration(manager.metadata.patchTime)*time.Nanosecond))
  181. fmt.Println("totalCloneTime: ", durafmt.Parse(time.Duration(manager.metadata.cloneTime)*time.Nanosecond))
  182. fmt.Println("totalCommits: ", manager.metadata.Commits)
  183. const padding = 6
  184. w := tabwriter.NewWriter(os.Stdout, 0, 0, padding, '.', 0)
  185. log.Debugf("--------------------------\n")
  186. log.Debugf("| Individual Regex Times |\n")
  187. log.Debugf("--------------------------\n")
  188. for k, v := range manager.metadata.RegexTime {
  189. fmt.Fprintf(w, "%s\t%s\n", k, durafmt.Parse(time.Duration(v)*time.Nanosecond))
  190. }
  191. w.Flush()
  192. }
  193. // Report saves gitleaks leaks to a json specified by --report={report.json}
  194. func (manager *Manager) Report() error {
  195. close(manager.leakChan)
  196. close(manager.metadata.timings)
  197. if log.IsLevelEnabled(log.DebugLevel) {
  198. manager.DebugOutput()
  199. }
  200. if manager.Opts.Report != "" {
  201. if len(manager.GetLeaks()) == 0 {
  202. log.Infof("no leaks found, skipping writing report")
  203. return nil
  204. }
  205. file, err := os.Create(manager.Opts.Report)
  206. if err != nil {
  207. return err
  208. }
  209. encoder := json.NewEncoder(file)
  210. encoder.SetIndent("", " ")
  211. err = encoder.Encode(manager.leaks)
  212. if err != nil {
  213. return err
  214. }
  215. err = file.Close()
  216. if err != nil {
  217. return err
  218. }
  219. log.Infof("report written to %s", manager.Opts.Report)
  220. }
  221. return nil
  222. }
  223. func (manager *Manager) receiveInterrupt() {
  224. <-manager.stopChan
  225. if manager.Opts.Report != "" {
  226. err := manager.Report()
  227. if err != nil {
  228. log.Error(err)
  229. }
  230. }
  231. log.Info("gitleaks received interrupt, stopping audit")
  232. os.Exit(options.ErrorEncountered)
  233. }