config.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. package config
  2. import (
  3. _ "embed"
  4. "fmt"
  5. "sort"
  6. "strings"
  7. "github.com/rs/zerolog/log"
  8. "github.com/spf13/viper"
  9. regexp "github.com/wasilibs/go-re2"
  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. Regex string
  27. SecretGroup int
  28. Entropy float64
  29. Keywords []string
  30. Path string
  31. Tags []string
  32. // Deprecated: this is a shim for backwards-compatibility. It should be removed in 9.x.
  33. AllowList *viperRuleAllowlist
  34. Allowlists []viperRuleAllowlist
  35. }
  36. Allowlist struct {
  37. Commits []string
  38. Paths []string
  39. RegexTarget string
  40. Regexes []string
  41. StopWords []string
  42. }
  43. }
  44. type viperRuleAllowlist struct {
  45. Description string
  46. Condition string
  47. Commits []string
  48. Paths []string
  49. RegexTarget string
  50. Regexes []string
  51. StopWords []string
  52. }
  53. // Config is a configuration struct that contains rules and an allowlist if present.
  54. type Config struct {
  55. Title string
  56. Extend Extend
  57. Path string
  58. Description string
  59. Rules map[string]Rule
  60. Allowlist Allowlist
  61. Keywords map[string]struct{}
  62. // used to keep sarif results consistent
  63. OrderedRules []string
  64. }
  65. // Extend is a struct that allows users to define how they want their
  66. // configuration extended by other configuration files.
  67. type Extend struct {
  68. Path string
  69. URL string
  70. UseDefault bool
  71. }
  72. func (vc *ViperConfig) Translate() (Config, error) {
  73. var (
  74. keywords = make(map[string]struct{})
  75. orderedRules []string
  76. rulesMap = make(map[string]Rule)
  77. )
  78. // Validate individual rules.
  79. for _, vr := range vc.Rules {
  80. if vr.Keywords == nil {
  81. vr.Keywords = []string{}
  82. } else {
  83. for i, k := range vr.Keywords {
  84. keyword := strings.ToLower(k)
  85. keywords[keyword] = struct{}{}
  86. vr.Keywords[i] = keyword
  87. }
  88. }
  89. if vr.Tags == nil {
  90. vr.Tags = []string{}
  91. }
  92. var configRegex *regexp.Regexp
  93. var configPathRegex *regexp.Regexp
  94. if vr.Regex != "" {
  95. configRegex = regexp.MustCompile(vr.Regex)
  96. }
  97. if vr.Path != "" {
  98. configPathRegex = regexp.MustCompile(vr.Path)
  99. }
  100. cr := Rule{
  101. RuleID: vr.ID,
  102. Description: vr.Description,
  103. Regex: configRegex,
  104. SecretGroup: vr.SecretGroup,
  105. Entropy: vr.Entropy,
  106. Path: configPathRegex,
  107. Keywords: vr.Keywords,
  108. Tags: vr.Tags,
  109. }
  110. // Parse the allowlist, including the older format for backwards compatibility.
  111. if vr.AllowList != nil {
  112. if len(vr.Allowlists) > 0 {
  113. return Config{}, fmt.Errorf("%s: [rules.allowlist] is deprecated, it cannot be used alongside [[rules.allowlist]]", cr.RuleID)
  114. }
  115. vr.Allowlists = append(vr.Allowlists, *vr.AllowList)
  116. }
  117. for _, a := range vr.Allowlists {
  118. var condition AllowlistMatchCondition
  119. c := strings.ToUpper(a.Condition)
  120. switch c {
  121. case "AND", "&&":
  122. condition = AllowlistMatchAnd
  123. case "", "OR", "||":
  124. condition = AllowlistMatchOr
  125. default:
  126. return Config{}, fmt.Errorf("%s: unknown allowlist condition '%s' (expected 'and', 'or')", cr.RuleID, c)
  127. }
  128. // Validate the target.
  129. if a.RegexTarget != "" {
  130. switch a.RegexTarget {
  131. case "secret":
  132. a.RegexTarget = ""
  133. case "match", "line":
  134. // do nothing
  135. default:
  136. return Config{}, fmt.Errorf("%s: unknown allowlist |regexTarget| '%s' (expected 'match', 'line')", cr.RuleID, a.RegexTarget)
  137. }
  138. }
  139. var allowlistRegexes []*regexp.Regexp
  140. for _, a := range a.Regexes {
  141. allowlistRegexes = append(allowlistRegexes, regexp.MustCompile(a))
  142. }
  143. var allowlistPaths []*regexp.Regexp
  144. for _, a := range a.Paths {
  145. allowlistPaths = append(allowlistPaths, regexp.MustCompile(a))
  146. }
  147. allowlist := Allowlist{
  148. MatchCondition: condition,
  149. RegexTarget: a.RegexTarget,
  150. Regexes: allowlistRegexes,
  151. Paths: allowlistPaths,
  152. Commits: a.Commits,
  153. StopWords: a.StopWords,
  154. }
  155. if err := allowlist.Validate(); err != nil {
  156. return Config{}, fmt.Errorf("%s: %w", cr.RuleID, err)
  157. }
  158. cr.Allowlists = append(cr.Allowlists, allowlist)
  159. }
  160. orderedRules = append(orderedRules, cr.RuleID)
  161. rulesMap[cr.RuleID] = cr
  162. }
  163. var allowlistRegexes []*regexp.Regexp
  164. for _, a := range vc.Allowlist.Regexes {
  165. allowlistRegexes = append(allowlistRegexes, regexp.MustCompile(a))
  166. }
  167. var allowlistPaths []*regexp.Regexp
  168. for _, a := range vc.Allowlist.Paths {
  169. allowlistPaths = append(allowlistPaths, regexp.MustCompile(a))
  170. }
  171. c := Config{
  172. Description: vc.Description,
  173. Extend: vc.Extend,
  174. Rules: rulesMap,
  175. Allowlist: Allowlist{
  176. RegexTarget: vc.Allowlist.RegexTarget,
  177. Regexes: allowlistRegexes,
  178. Paths: allowlistPaths,
  179. Commits: vc.Allowlist.Commits,
  180. StopWords: vc.Allowlist.StopWords,
  181. },
  182. Keywords: keywords,
  183. OrderedRules: orderedRules,
  184. }
  185. if maxExtendDepth != extendDepth {
  186. // disallow both usedefault and path from being set
  187. if c.Extend.Path != "" && c.Extend.UseDefault {
  188. log.Fatal().Msg("unable to load config due to extend.path and extend.useDefault being set")
  189. }
  190. if c.Extend.UseDefault {
  191. c.extendDefault()
  192. } else if c.Extend.Path != "" {
  193. c.extendPath()
  194. }
  195. }
  196. // Validate the rules after everything has been assembled (including extended configs).
  197. if extendDepth == 0 {
  198. for _, rule := range rulesMap {
  199. if err := rule.Validate(); err != nil {
  200. return Config{}, err
  201. }
  202. }
  203. }
  204. return c, nil
  205. }
  206. func (c *Config) GetOrderedRules() []Rule {
  207. var orderedRules []Rule
  208. for _, id := range c.OrderedRules {
  209. if _, ok := c.Rules[id]; ok {
  210. orderedRules = append(orderedRules, c.Rules[id])
  211. }
  212. }
  213. return orderedRules
  214. }
  215. func (c *Config) extendDefault() {
  216. extendDepth++
  217. viper.SetConfigType("toml")
  218. if err := viper.ReadConfig(strings.NewReader(DefaultConfig)); err != nil {
  219. log.Fatal().Msgf("failed to load extended config, err: %s", err)
  220. return
  221. }
  222. defaultViperConfig := ViperConfig{}
  223. if err := viper.Unmarshal(&defaultViperConfig); err != nil {
  224. log.Fatal().Msgf("failed to load extended config, err: %s", err)
  225. return
  226. }
  227. cfg, err := defaultViperConfig.Translate()
  228. if err != nil {
  229. log.Fatal().Msgf("failed to load extended config, err: %s", err)
  230. return
  231. }
  232. log.Debug().Msg("extending config with default config")
  233. c.extend(cfg)
  234. }
  235. func (c *Config) extendPath() {
  236. extendDepth++
  237. viper.SetConfigFile(c.Extend.Path)
  238. if err := viper.ReadInConfig(); err != nil {
  239. log.Fatal().Msgf("failed to load extended config, err: %s", err)
  240. return
  241. }
  242. extensionViperConfig := ViperConfig{}
  243. if err := viper.Unmarshal(&extensionViperConfig); err != nil {
  244. log.Fatal().Msgf("failed to load extended config, err: %s", err)
  245. return
  246. }
  247. cfg, err := extensionViperConfig.Translate()
  248. if err != nil {
  249. log.Fatal().Msgf("failed to load extended config, err: %s", err)
  250. return
  251. }
  252. log.Debug().Msgf("extending config with %s", c.Extend.Path)
  253. c.extend(cfg)
  254. }
  255. func (c *Config) extendURL() {
  256. // TODO
  257. }
  258. func (c *Config) extend(extensionConfig Config) {
  259. for ruleID, baseRule := range extensionConfig.Rules {
  260. currentRule, ok := c.Rules[ruleID]
  261. if !ok {
  262. // Rule doesn't exist, add it to the config.
  263. c.Rules[ruleID] = baseRule
  264. for _, k := range baseRule.Keywords {
  265. c.Keywords[k] = struct{}{}
  266. }
  267. c.OrderedRules = append(c.OrderedRules, ruleID)
  268. } else {
  269. // Rule exists, merge our changes into the base.
  270. if currentRule.Description != "" {
  271. baseRule.Description = currentRule.Description
  272. }
  273. if currentRule.Entropy != 0 {
  274. baseRule.Entropy = currentRule.Entropy
  275. }
  276. if currentRule.SecretGroup != 0 {
  277. baseRule.SecretGroup = currentRule.SecretGroup
  278. }
  279. if currentRule.Regex != nil {
  280. baseRule.Regex = currentRule.Regex
  281. }
  282. if currentRule.Path != nil {
  283. baseRule.Path = currentRule.Path
  284. }
  285. baseRule.Tags = append(baseRule.Tags, currentRule.Tags...)
  286. baseRule.Keywords = append(baseRule.Keywords, currentRule.Keywords...)
  287. for _, a := range currentRule.Allowlists {
  288. baseRule.Allowlists = append(baseRule.Allowlists, a)
  289. }
  290. // The keywords from the base rule and the extended rule must be merged into the global keywords list
  291. for _, k := range baseRule.Keywords {
  292. c.Keywords[k] = struct{}{}
  293. }
  294. c.Rules[ruleID] = baseRule
  295. }
  296. }
  297. // append allowlists, not attempting to merge
  298. c.Allowlist.Commits = append(c.Allowlist.Commits,
  299. extensionConfig.Allowlist.Commits...)
  300. c.Allowlist.Paths = append(c.Allowlist.Paths,
  301. extensionConfig.Allowlist.Paths...)
  302. c.Allowlist.Regexes = append(c.Allowlist.Regexes,
  303. extensionConfig.Allowlist.Regexes...)
  304. // sort to keep extended rules in order
  305. sort.Strings(c.OrderedRules)
  306. }