config.go 7.0 KB

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