config.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. package config
  2. import (
  3. "fmt"
  4. "path"
  5. "regexp"
  6. "strconv"
  7. "github.com/zricethezav/gitleaks/v7/options"
  8. "github.com/BurntSushi/toml"
  9. "github.com/go-git/go-git/v5"
  10. log "github.com/sirupsen/logrus"
  11. )
  12. // Config is a composite struct of Rules and Allowlists
  13. // Each Rule contains a description, regular expression, tags, and allowlists if available
  14. type Config struct {
  15. Rules []Rule
  16. Allowlist AllowList
  17. }
  18. // Entropy represents an entropy range
  19. type Entropy struct {
  20. Min float64
  21. Max float64
  22. Group int
  23. }
  24. // TomlAllowList is a struct used in the TomlLoader that loads in allowlists from
  25. // specific rules or globally at the top level config
  26. type TomlAllowList struct {
  27. Description string
  28. Regexes []string
  29. Commits []string
  30. Files []string
  31. Paths []string
  32. Repos []string
  33. }
  34. // TomlLoader gets loaded with the values from a gitleaks toml config
  35. // see the config in config/defaults.go for an example. TomlLoader is used
  36. // to generate Config values (compiling regexes, etc).
  37. type TomlLoader struct {
  38. AllowList TomlAllowList
  39. Rules []struct {
  40. Description string
  41. Regex string
  42. File string
  43. Path string
  44. ReportGroup int
  45. Tags []string
  46. Entropies []struct {
  47. Min string
  48. Max string
  49. Group string
  50. }
  51. AllowList TomlAllowList
  52. }
  53. }
  54. // NewConfig will create a new config struct which contains
  55. // rules on how gitleaks will proceed with its scan.
  56. // If no options are passed via cli then NewConfig will return
  57. // a default config which can be seen in config.go
  58. func NewConfig(options options.Options) (Config, error) {
  59. var cfg Config
  60. tomlLoader := TomlLoader{}
  61. var err error
  62. if options.ConfigPath != "" {
  63. _, err = toml.DecodeFile(options.ConfigPath, &tomlLoader)
  64. // append a allowlist rule for allowlisting the config
  65. tomlLoader.AllowList.Files = append(tomlLoader.AllowList.Files, path.Base(options.ConfigPath))
  66. } else {
  67. _, err = toml.Decode(DefaultConfig, &tomlLoader)
  68. }
  69. if err != nil {
  70. return cfg, err
  71. }
  72. cfg, err = tomlLoader.Parse()
  73. if err != nil {
  74. return cfg, err
  75. }
  76. return cfg, nil
  77. }
  78. // Parse will parse the values set in a TomlLoader and use those values
  79. // to create compiled regular expressions and rules used in scans
  80. func (tomlLoader TomlLoader) Parse() (Config, error) {
  81. var cfg Config
  82. for _, rule := range tomlLoader.Rules {
  83. // check and make sure the rule is valid
  84. if rule.Regex == "" && rule.Path == "" && rule.File == "" && len(rule.Entropies) == 0 {
  85. log.Warnf("Rule %s does not define any actionable data", rule.Description)
  86. continue
  87. }
  88. re, err := regexp.Compile(rule.Regex)
  89. if err != nil {
  90. return cfg, fmt.Errorf("problem loading config: %v", err)
  91. }
  92. fileNameRe, err := regexp.Compile(rule.File)
  93. if err != nil {
  94. return cfg, fmt.Errorf("problem loading config: %v", err)
  95. }
  96. filePathRe, err := regexp.Compile(rule.Path)
  97. if err != nil {
  98. return cfg, fmt.Errorf("problem loading config: %v", err)
  99. }
  100. // rule specific allowlists
  101. var allowList AllowList
  102. allowList.Description = rule.AllowList.Description
  103. // rule specific regexes
  104. for _, re := range rule.AllowList.Regexes {
  105. allowListedRegex, err := regexp.Compile(re)
  106. if err != nil {
  107. return cfg, fmt.Errorf("problem loading config: %v", err)
  108. }
  109. allowList.Regexes = append(allowList.Regexes, allowListedRegex)
  110. }
  111. // rule specific filenames
  112. for _, re := range rule.AllowList.Files {
  113. allowListedRegex, err := regexp.Compile(re)
  114. if err != nil {
  115. return cfg, fmt.Errorf("problem loading config: %v", err)
  116. }
  117. allowList.Files = append(allowList.Files, allowListedRegex)
  118. }
  119. // rule specific paths
  120. for _, re := range rule.AllowList.Paths {
  121. allowListedRegex, err := regexp.Compile(re)
  122. if err != nil {
  123. return cfg, fmt.Errorf("problem loading config: %v", err)
  124. }
  125. allowList.Paths = append(allowList.Paths, allowListedRegex)
  126. }
  127. // rule specific commits
  128. allowList.Commits = rule.AllowList.Commits
  129. var entropies []Entropy
  130. for _, e := range rule.Entropies {
  131. min, err := strconv.ParseFloat(e.Min, 64)
  132. if err != nil {
  133. return cfg, err
  134. }
  135. max, err := strconv.ParseFloat(e.Max, 64)
  136. if err != nil {
  137. return cfg, err
  138. }
  139. if e.Group == "" {
  140. e.Group = "0"
  141. }
  142. group, err := strconv.ParseInt(e.Group, 10, 64)
  143. if err != nil {
  144. return cfg, err
  145. } else if int(group) >= len(re.SubexpNames()) {
  146. return cfg, fmt.Errorf("problem loading config: group cannot be higher than number of groups in regexp")
  147. } else if group < 0 {
  148. return cfg, fmt.Errorf("problem loading config: group cannot be lower than 0")
  149. } else if min > 8.0 || min < 0.0 || max > 8.0 || max < 0.0 {
  150. return cfg, fmt.Errorf("problem loading config: invalid entropy ranges, must be within 0.0-8.0")
  151. } else if min > max {
  152. return cfg, fmt.Errorf("problem loading config: entropy Min value cannot be higher than Max value")
  153. }
  154. entropies = append(entropies, Entropy{Min: min, Max: max, Group: int(group)})
  155. }
  156. r := Rule{
  157. Description: rule.Description,
  158. Regex: re,
  159. File: fileNameRe,
  160. Path: filePathRe,
  161. ReportGroup: rule.ReportGroup,
  162. Tags: rule.Tags,
  163. AllowList: allowList,
  164. Entropies: entropies,
  165. }
  166. cfg.Rules = append(cfg.Rules, r)
  167. }
  168. // global regex allowLists
  169. for _, allowListRegex := range tomlLoader.AllowList.Regexes {
  170. re, err := regexp.Compile(allowListRegex)
  171. if err != nil {
  172. return cfg, fmt.Errorf("problem loading config: %v", err)
  173. }
  174. cfg.Allowlist.Regexes = append(cfg.Allowlist.Regexes, re)
  175. }
  176. // global file name allowLists
  177. for _, allowListFileName := range tomlLoader.AllowList.Files {
  178. re, err := regexp.Compile(allowListFileName)
  179. if err != nil {
  180. return cfg, fmt.Errorf("problem loading config: %v", err)
  181. }
  182. cfg.Allowlist.Files = append(cfg.Allowlist.Files, re)
  183. }
  184. // global file path allowLists
  185. for _, allowListFilePath := range tomlLoader.AllowList.Paths {
  186. re, err := regexp.Compile(allowListFilePath)
  187. if err != nil {
  188. return cfg, fmt.Errorf("problem loading config: %v", err)
  189. }
  190. cfg.Allowlist.Paths = append(cfg.Allowlist.Paths, re)
  191. }
  192. // global repo allowLists
  193. for _, allowListRepo := range tomlLoader.AllowList.Repos {
  194. re, err := regexp.Compile(allowListRepo)
  195. if err != nil {
  196. return cfg, fmt.Errorf("problem loading config: %v", err)
  197. }
  198. cfg.Allowlist.Repos = append(cfg.Allowlist.Repos, re)
  199. }
  200. cfg.Allowlist.Commits = tomlLoader.AllowList.Commits
  201. cfg.Allowlist.Description = tomlLoader.AllowList.Description
  202. return cfg, nil
  203. }
  204. // LoadRepoConfig accepts a repo and config path related to the target repo's root.
  205. func LoadRepoConfig(repo *git.Repository, repoConfig string) (Config, error) {
  206. gitRepoConfig, err := repo.Config()
  207. if err != nil {
  208. return Config{}, err
  209. }
  210. if !gitRepoConfig.Core.IsBare {
  211. wt, err := repo.Worktree()
  212. if err != nil {
  213. return Config{}, err
  214. }
  215. _, err = wt.Filesystem.Stat(repoConfig)
  216. if err != nil {
  217. return Config{}, err
  218. }
  219. r, err := wt.Filesystem.Open(repoConfig)
  220. if err != nil {
  221. return Config{}, err
  222. }
  223. var tomlLoader TomlLoader
  224. _, err = toml.DecodeReader(r, &tomlLoader)
  225. if err != nil {
  226. return Config{}, err
  227. }
  228. return tomlLoader.Parse()
  229. }
  230. log.Debug("attempting to load repo config from bare worktree, this may use an old config")
  231. ref, err := repo.Head()
  232. if err != nil {
  233. return Config{}, err
  234. }
  235. c, err := repo.CommitObject(ref.Hash())
  236. if err != nil {
  237. return Config{}, err
  238. }
  239. f, err := c.File(repoConfig)
  240. if err != nil {
  241. return Config{}, err
  242. }
  243. var tomlLoader TomlLoader
  244. r, err := f.Reader()
  245. if err != nil {
  246. return Config{}, err
  247. }
  248. _, err = toml.DecodeReader(r, &tomlLoader)
  249. if err != nil {
  250. return Config{}, err
  251. }
  252. return tomlLoader.Parse()
  253. }