4
0

config.go 8.5 KB

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