config.go 8.8 KB

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