config.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. package config
  2. import (
  3. _ "embed"
  4. "fmt"
  5. "regexp"
  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. Regexes []string
  33. Paths []string
  34. Commits []string
  35. StopWords []string
  36. }
  37. }
  38. Allowlist struct {
  39. Regexes []string
  40. Paths []string
  41. Commits []string
  42. StopWords []string
  43. }
  44. }
  45. // Config is a configuration struct that contains rules and an allowlist if present.
  46. type Config struct {
  47. Extend Extend
  48. Path string
  49. Description string
  50. Rules map[string]Rule
  51. Allowlist Allowlist
  52. Keywords []string
  53. // used to keep sarif results consistent
  54. orderedRules []string
  55. }
  56. // Extend is a struct that allows users to define how they want their
  57. // configuration extended by other configuration files.
  58. type Extend struct {
  59. Path string
  60. URL string
  61. UseDefault bool
  62. }
  63. func (vc *ViperConfig) Translate() (Config, error) {
  64. var (
  65. keywords []string
  66. orderedRules []string
  67. )
  68. rulesMap := make(map[string]Rule)
  69. for _, r := range vc.Rules {
  70. var allowlistRegexes []*regexp.Regexp
  71. for _, a := range r.Allowlist.Regexes {
  72. allowlistRegexes = append(allowlistRegexes, regexp.MustCompile(a))
  73. }
  74. var allowlistPaths []*regexp.Regexp
  75. for _, a := range r.Allowlist.Paths {
  76. allowlistPaths = append(allowlistPaths, regexp.MustCompile(a))
  77. }
  78. if r.Keywords == nil {
  79. r.Keywords = []string{}
  80. } else {
  81. for _, k := range r.Keywords {
  82. keywords = append(keywords, strings.ToLower(k))
  83. }
  84. }
  85. if r.Tags == nil {
  86. r.Tags = []string{}
  87. }
  88. var configRegex *regexp.Regexp
  89. var configPathRegex *regexp.Regexp
  90. if r.Regex == "" {
  91. configRegex = nil
  92. } else {
  93. configRegex = regexp.MustCompile(r.Regex)
  94. }
  95. if r.Path == "" {
  96. configPathRegex = nil
  97. } else {
  98. configPathRegex = regexp.MustCompile(r.Path)
  99. }
  100. r := Rule{
  101. Description: r.Description,
  102. RuleID: r.ID,
  103. Regex: configRegex,
  104. Path: configPathRegex,
  105. SecretGroup: r.SecretGroup,
  106. Entropy: r.Entropy,
  107. Tags: r.Tags,
  108. Keywords: r.Keywords,
  109. Allowlist: Allowlist{
  110. Regexes: allowlistRegexes,
  111. Paths: allowlistPaths,
  112. Commits: r.Allowlist.Commits,
  113. StopWords: r.Allowlist.StopWords,
  114. },
  115. }
  116. orderedRules = append(orderedRules, r.RuleID)
  117. if r.Regex != nil && r.SecretGroup > r.Regex.NumSubexp() {
  118. return Config{}, fmt.Errorf("%s invalid regex secret group %d, max regex secret group %d", r.Description, r.SecretGroup, r.Regex.NumSubexp())
  119. }
  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. Regexes: allowlistRegexes,
  136. Paths: allowlistPaths,
  137. Commits: vc.Allowlist.Commits,
  138. StopWords: vc.Allowlist.StopWords,
  139. },
  140. Keywords: keywords,
  141. orderedRules: orderedRules,
  142. }
  143. if maxExtendDepth != extendDepth {
  144. // disallow both usedefault and path from being set
  145. if c.Extend.Path != "" && c.Extend.UseDefault {
  146. log.Fatal().Msg("unable to load config due to extend.path and extend.useDefault being set")
  147. }
  148. if c.Extend.UseDefault {
  149. c.extendDefault()
  150. } else if c.Extend.Path != "" {
  151. c.extendPath()
  152. }
  153. }
  154. return c, nil
  155. }
  156. func (c *Config) OrderedRules() []Rule {
  157. var orderedRules []Rule
  158. for _, id := range c.orderedRules {
  159. if _, ok := c.Rules[id]; ok {
  160. orderedRules = append(orderedRules, c.Rules[id])
  161. }
  162. }
  163. return orderedRules
  164. }
  165. func (c *Config) extendDefault() {
  166. extendDepth++
  167. viper.SetConfigType("toml")
  168. if err := viper.ReadConfig(strings.NewReader(DefaultConfig)); err != nil {
  169. log.Fatal().Msgf("failed to load extended config, err: %s", err)
  170. return
  171. }
  172. defaultViperConfig := ViperConfig{}
  173. if err := viper.Unmarshal(&defaultViperConfig); err != nil {
  174. log.Fatal().Msgf("failed to load extended config, err: %s", err)
  175. return
  176. }
  177. cfg, err := defaultViperConfig.Translate()
  178. if err != nil {
  179. log.Fatal().Msgf("failed to load extended config, err: %s", err)
  180. return
  181. }
  182. log.Debug().Msg("extending config with default config")
  183. c.extend(cfg)
  184. }
  185. func (c *Config) extendPath() {
  186. extendDepth++
  187. viper.SetConfigFile(c.Extend.Path)
  188. if err := viper.ReadInConfig(); err != nil {
  189. log.Fatal().Msgf("failed to load extended config, err: %s", err)
  190. return
  191. }
  192. extensionViperConfig := ViperConfig{}
  193. if err := viper.Unmarshal(&extensionViperConfig); err != nil {
  194. log.Fatal().Msgf("failed to load extended config, err: %s", err)
  195. return
  196. }
  197. cfg, err := extensionViperConfig.Translate()
  198. if err != nil {
  199. log.Fatal().Msgf("failed to load extended config, err: %s", err)
  200. return
  201. }
  202. log.Debug().Msgf("extending config with %s", c.Extend.Path)
  203. c.extend(cfg)
  204. }
  205. func (c *Config) extendURL() {
  206. // TODO
  207. }
  208. func (c *Config) extend(extensionConfig Config) {
  209. for ruleID, rule := range extensionConfig.Rules {
  210. if _, ok := c.Rules[ruleID]; !ok {
  211. log.Trace().Msgf("adding %s to base config", ruleID)
  212. c.Rules[ruleID] = rule
  213. c.Keywords = append(c.Keywords, rule.Keywords...)
  214. }
  215. }
  216. // append allowlists, not attempting to merge
  217. c.Allowlist.Commits = append(c.Allowlist.Commits,
  218. extensionConfig.Allowlist.Commits...)
  219. c.Allowlist.Paths = append(c.Allowlist.Paths,
  220. extensionConfig.Allowlist.Paths...)
  221. c.Allowlist.Regexes = append(c.Allowlist.Regexes,
  222. extensionConfig.Allowlist.Regexes...)
  223. }