config.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  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. var allowlistRegexes []*regexp.Regexp
  74. for _, a := range r.Allowlist.Regexes {
  75. allowlistRegexes = append(allowlistRegexes, regexp.MustCompile(a))
  76. }
  77. var allowlistPaths []*regexp.Regexp
  78. for _, a := range r.Allowlist.Paths {
  79. allowlistPaths = append(allowlistPaths, regexp.MustCompile(a))
  80. }
  81. if r.Keywords == nil {
  82. r.Keywords = []string{}
  83. } else {
  84. for _, k := range r.Keywords {
  85. keywords = append(keywords, strings.ToLower(k))
  86. }
  87. }
  88. if r.Tags == nil {
  89. r.Tags = []string{}
  90. }
  91. var configRegex *regexp.Regexp
  92. var configPathRegex *regexp.Regexp
  93. if r.Regex == "" {
  94. configRegex = nil
  95. } else {
  96. configRegex = regexp.MustCompile(r.Regex)
  97. }
  98. if r.Path == "" {
  99. configPathRegex = nil
  100. } else {
  101. configPathRegex = regexp.MustCompile(r.Path)
  102. }
  103. r := Rule{
  104. Description: r.Description,
  105. RuleID: r.ID,
  106. Regex: configRegex,
  107. Path: configPathRegex,
  108. SecretGroup: r.SecretGroup,
  109. Entropy: r.Entropy,
  110. Tags: r.Tags,
  111. Keywords: r.Keywords,
  112. Allowlist: Allowlist{
  113. RegexTarget: r.Allowlist.RegexTarget,
  114. Regexes: allowlistRegexes,
  115. Paths: allowlistPaths,
  116. Commits: r.Allowlist.Commits,
  117. StopWords: r.Allowlist.StopWords,
  118. },
  119. }
  120. orderedRules = append(orderedRules, r.RuleID)
  121. if r.Regex != nil && r.SecretGroup > r.Regex.NumSubexp() {
  122. return Config{}, fmt.Errorf("%s invalid regex secret group %d, max regex secret group %d", r.Description, r.SecretGroup, r.Regex.NumSubexp())
  123. }
  124. rulesMap[r.RuleID] = r
  125. }
  126. var allowlistRegexes []*regexp.Regexp
  127. for _, a := range vc.Allowlist.Regexes {
  128. allowlistRegexes = append(allowlistRegexes, regexp.MustCompile(a))
  129. }
  130. var allowlistPaths []*regexp.Regexp
  131. for _, a := range vc.Allowlist.Paths {
  132. allowlistPaths = append(allowlistPaths, regexp.MustCompile(a))
  133. }
  134. c := Config{
  135. Description: vc.Description,
  136. Extend: vc.Extend,
  137. Rules: rulesMap,
  138. Allowlist: Allowlist{
  139. RegexTarget: vc.Allowlist.RegexTarget,
  140. Regexes: allowlistRegexes,
  141. Paths: allowlistPaths,
  142. Commits: vc.Allowlist.Commits,
  143. StopWords: vc.Allowlist.StopWords,
  144. },
  145. Keywords: keywords,
  146. OrderedRules: orderedRules,
  147. }
  148. if maxExtendDepth != extendDepth {
  149. // disallow both usedefault and path from being set
  150. if c.Extend.Path != "" && c.Extend.UseDefault {
  151. log.Fatal().Msg("unable to load config due to extend.path and extend.useDefault being set")
  152. }
  153. if c.Extend.UseDefault {
  154. c.extendDefault()
  155. } else if c.Extend.Path != "" {
  156. c.extendPath()
  157. }
  158. }
  159. return c, nil
  160. }
  161. func (c *Config) GetOrderedRules() []Rule {
  162. var orderedRules []Rule
  163. for _, id := range c.OrderedRules {
  164. if _, ok := c.Rules[id]; ok {
  165. orderedRules = append(orderedRules, c.Rules[id])
  166. }
  167. }
  168. return orderedRules
  169. }
  170. func (c *Config) extendDefault() {
  171. extendDepth++
  172. viper.SetConfigType("toml")
  173. if err := viper.ReadConfig(strings.NewReader(DefaultConfig)); err != nil {
  174. log.Fatal().Msgf("failed to load extended config, err: %s", err)
  175. return
  176. }
  177. defaultViperConfig := ViperConfig{}
  178. if err := viper.Unmarshal(&defaultViperConfig); err != nil {
  179. log.Fatal().Msgf("failed to load extended config, err: %s", err)
  180. return
  181. }
  182. cfg, err := defaultViperConfig.Translate()
  183. if err != nil {
  184. log.Fatal().Msgf("failed to load extended config, err: %s", err)
  185. return
  186. }
  187. log.Debug().Msg("extending config with default config")
  188. c.extend(cfg)
  189. }
  190. func (c *Config) extendPath() {
  191. extendDepth++
  192. viper.SetConfigFile(c.Extend.Path)
  193. if err := viper.ReadInConfig(); err != nil {
  194. log.Fatal().Msgf("failed to load extended config, err: %s", err)
  195. return
  196. }
  197. extensionViperConfig := ViperConfig{}
  198. if err := viper.Unmarshal(&extensionViperConfig); err != nil {
  199. log.Fatal().Msgf("failed to load extended config, err: %s", err)
  200. return
  201. }
  202. cfg, err := extensionViperConfig.Translate()
  203. if err != nil {
  204. log.Fatal().Msgf("failed to load extended config, err: %s", err)
  205. return
  206. }
  207. log.Debug().Msgf("extending config with %s", c.Extend.Path)
  208. c.extend(cfg)
  209. }
  210. func (c *Config) extendURL() {
  211. // TODO
  212. }
  213. func (c *Config) extend(extensionConfig Config) {
  214. for ruleID, rule := range extensionConfig.Rules {
  215. if _, ok := c.Rules[ruleID]; !ok {
  216. log.Trace().Msgf("adding %s to base config", ruleID)
  217. c.Rules[ruleID] = rule
  218. c.Keywords = append(c.Keywords, rule.Keywords...)
  219. c.OrderedRules = append(c.OrderedRules, ruleID)
  220. }
  221. }
  222. // append allowlists, not attempting to merge
  223. c.Allowlist.Commits = append(c.Allowlist.Commits,
  224. extensionConfig.Allowlist.Commits...)
  225. c.Allowlist.Paths = append(c.Allowlist.Paths,
  226. extensionConfig.Allowlist.Paths...)
  227. c.Allowlist.Regexes = append(c.Allowlist.Regexes,
  228. extensionConfig.Allowlist.Regexes...)
  229. // sort to keep extended rules in order
  230. sort.Strings(c.OrderedRules)
  231. }