config.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. package config
  2. import (
  3. _ "embed"
  4. "fmt"
  5. "regexp"
  6. "sort"
  7. "strings"
  8. "github.com/rs/zerolog/log"
  9. "github.com/spf13/viper"
  10. )
  11. //go:embed gitleaks.toml
  12. var DefaultConfig string
  13. // use to keep track of how many configs we can extend
  14. // yea I know, globals bad
  15. var extendDepth int
  16. const maxExtendDepth = 2
  17. // ViperConfig is the config struct used by the Viper config package
  18. // to parse the config file. This struct does not include regular expressions.
  19. // It is used as an intermediary to convert the Viper config to the Config struct.
  20. type ViperConfig struct {
  21. Description string
  22. Extend Extend
  23. Rules []struct {
  24. ID string
  25. Description string
  26. Entropy float64
  27. SecretGroup int
  28. Regex string
  29. Keywords []string
  30. Path string
  31. Tags []string
  32. Allowlist struct {
  33. RegexTarget string
  34. Regexes []string
  35. Paths []string
  36. Commits []string
  37. StopWords []string
  38. }
  39. }
  40. Allowlist struct {
  41. RegexTarget string
  42. Regexes []string
  43. Paths []string
  44. Commits []string
  45. StopWords []string
  46. }
  47. }
  48. // Config is a configuration struct that contains rules and an allowlist if present.
  49. type Config struct {
  50. Extend Extend
  51. Path string
  52. Description string
  53. Rules map[string]Rule
  54. Allowlist Allowlist
  55. Keywords []string
  56. // used to keep sarif results consistent
  57. OrderedRules []string
  58. }
  59. // Extend is a struct that allows users to define how they want their
  60. // configuration extended by other configuration files.
  61. type Extend struct {
  62. Path string
  63. URL string
  64. UseDefault bool
  65. }
  66. func (vc *ViperConfig) Translate() (Config, error) {
  67. var (
  68. keywords []string
  69. orderedRules []string
  70. )
  71. rulesMap := make(map[string]Rule)
  72. for _, r := range vc.Rules {
  73. if strings.TrimSpace(r.ID) == "" {
  74. return Config{}, fmt.Errorf("rule ID is missing or empty, regex: %s", r.Regex)
  75. }
  76. var allowlistRegexes []*regexp.Regexp
  77. for _, a := range r.Allowlist.Regexes {
  78. allowlistRegexes = append(allowlistRegexes, regexp.MustCompile(a))
  79. }
  80. var allowlistPaths []*regexp.Regexp
  81. for _, a := range r.Allowlist.Paths {
  82. allowlistPaths = append(allowlistPaths, regexp.MustCompile(a))
  83. }
  84. if r.Keywords == nil {
  85. r.Keywords = []string{}
  86. } else {
  87. for _, k := range r.Keywords {
  88. keywords = append(keywords, strings.ToLower(k))
  89. }
  90. }
  91. if r.Tags == nil {
  92. r.Tags = []string{}
  93. }
  94. var configRegex *regexp.Regexp
  95. var configPathRegex *regexp.Regexp
  96. if r.Regex == "" {
  97. configRegex = nil
  98. } else {
  99. configRegex = regexp.MustCompile(r.Regex)
  100. }
  101. if r.Path == "" {
  102. configPathRegex = nil
  103. } else {
  104. configPathRegex = regexp.MustCompile(r.Path)
  105. }
  106. r := Rule{
  107. Description: r.Description,
  108. RuleID: r.ID,
  109. Regex: configRegex,
  110. Path: configPathRegex,
  111. SecretGroup: r.SecretGroup,
  112. Entropy: r.Entropy,
  113. Tags: r.Tags,
  114. Keywords: r.Keywords,
  115. Allowlist: Allowlist{
  116. RegexTarget: r.Allowlist.RegexTarget,
  117. Regexes: allowlistRegexes,
  118. Paths: allowlistPaths,
  119. Commits: r.Allowlist.Commits,
  120. StopWords: r.Allowlist.StopWords,
  121. },
  122. }
  123. orderedRules = append(orderedRules, r.RuleID)
  124. if r.Regex != nil && r.SecretGroup > r.Regex.NumSubexp() {
  125. return Config{}, fmt.Errorf("%s: invalid regex secret group %d, max regex secret group %d", r.RuleID, r.SecretGroup, r.Regex.NumSubexp())
  126. }
  127. rulesMap[r.RuleID] = r
  128. }
  129. var allowlistRegexes []*regexp.Regexp
  130. for _, a := range vc.Allowlist.Regexes {
  131. allowlistRegexes = append(allowlistRegexes, regexp.MustCompile(a))
  132. }
  133. var allowlistPaths []*regexp.Regexp
  134. for _, a := range vc.Allowlist.Paths {
  135. allowlistPaths = append(allowlistPaths, regexp.MustCompile(a))
  136. }
  137. c := Config{
  138. Description: vc.Description,
  139. Extend: vc.Extend,
  140. Rules: rulesMap,
  141. Allowlist: Allowlist{
  142. RegexTarget: vc.Allowlist.RegexTarget,
  143. Regexes: allowlistRegexes,
  144. Paths: allowlistPaths,
  145. Commits: vc.Allowlist.Commits,
  146. StopWords: vc.Allowlist.StopWords,
  147. },
  148. Keywords: keywords,
  149. OrderedRules: orderedRules,
  150. }
  151. if maxExtendDepth != extendDepth {
  152. // disallow both usedefault and path from being set
  153. if c.Extend.Path != "" && c.Extend.UseDefault {
  154. log.Fatal().Msg("unable to load config due to extend.path and extend.useDefault being set")
  155. }
  156. if c.Extend.UseDefault {
  157. c.extendDefault()
  158. } else if c.Extend.Path != "" {
  159. c.extendPath()
  160. }
  161. }
  162. return c, nil
  163. }
  164. func (c *Config) GetOrderedRules() []Rule {
  165. var orderedRules []Rule
  166. for _, id := range c.OrderedRules {
  167. if _, ok := c.Rules[id]; ok {
  168. orderedRules = append(orderedRules, c.Rules[id])
  169. }
  170. }
  171. return orderedRules
  172. }
  173. func (c *Config) extendDefault() {
  174. extendDepth++
  175. viper.SetConfigType("toml")
  176. if err := viper.ReadConfig(strings.NewReader(DefaultConfig)); err != nil {
  177. log.Fatal().Msgf("failed to load extended config, err: %s", err)
  178. return
  179. }
  180. defaultViperConfig := ViperConfig{}
  181. if err := viper.Unmarshal(&defaultViperConfig); err != nil {
  182. log.Fatal().Msgf("failed to load extended config, err: %s", err)
  183. return
  184. }
  185. cfg, err := defaultViperConfig.Translate()
  186. if err != nil {
  187. log.Fatal().Msgf("failed to load extended config, err: %s", err)
  188. return
  189. }
  190. log.Debug().Msg("extending config with default config")
  191. c.extend(cfg)
  192. }
  193. func (c *Config) extendPath() {
  194. extendDepth++
  195. viper.SetConfigFile(c.Extend.Path)
  196. if err := viper.ReadInConfig(); err != nil {
  197. log.Fatal().Msgf("failed to load extended config, err: %s", err)
  198. return
  199. }
  200. extensionViperConfig := ViperConfig{}
  201. if err := viper.Unmarshal(&extensionViperConfig); err != nil {
  202. log.Fatal().Msgf("failed to load extended config, err: %s", err)
  203. return
  204. }
  205. cfg, err := extensionViperConfig.Translate()
  206. if err != nil {
  207. log.Fatal().Msgf("failed to load extended config, err: %s", err)
  208. return
  209. }
  210. log.Debug().Msgf("extending config with %s", c.Extend.Path)
  211. c.extend(cfg)
  212. }
  213. func (c *Config) extendURL() {
  214. // TODO
  215. }
  216. func (c *Config) extend(extensionConfig Config) {
  217. for ruleID, rule := range extensionConfig.Rules {
  218. if _, ok := c.Rules[ruleID]; !ok {
  219. log.Trace().Msgf("adding %s to base config", ruleID)
  220. c.Rules[ruleID] = rule
  221. c.Keywords = append(c.Keywords, rule.Keywords...)
  222. c.OrderedRules = append(c.OrderedRules, ruleID)
  223. }
  224. }
  225. // append allowlists, not attempting to merge
  226. c.Allowlist.Commits = append(c.Allowlist.Commits,
  227. extensionConfig.Allowlist.Commits...)
  228. c.Allowlist.Paths = append(c.Allowlist.Paths,
  229. extensionConfig.Allowlist.Paths...)
  230. c.Allowlist.Regexes = append(c.Allowlist.Regexes,
  231. extensionConfig.Allowlist.Regexes...)
  232. // sort to keep extended rules in order
  233. sort.Strings(c.OrderedRules)
  234. }