config.go 8.8 KB

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