config.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  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. 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 _, k := range vr.Keywords {
  84. keywords[strings.ToLower(k)] = struct{}{}
  85. }
  86. }
  87. if vr.Tags == nil {
  88. vr.Tags = []string{}
  89. }
  90. var configRegex *regexp.Regexp
  91. var configPathRegex *regexp.Regexp
  92. if vr.Regex != "" {
  93. configRegex = regexp.MustCompile(vr.Regex)
  94. }
  95. if vr.Path != "" {
  96. configPathRegex = regexp.MustCompile(vr.Path)
  97. }
  98. rule := Rule{
  99. RuleID: vr.ID,
  100. Description: vr.Description,
  101. Regex: configRegex,
  102. SecretGroup: vr.SecretGroup,
  103. Entropy: vr.Entropy,
  104. Path: configPathRegex,
  105. Keywords: vr.Keywords,
  106. Tags: vr.Tags,
  107. }
  108. // Parse the allowlist, including the older format for backwards compatibility.
  109. if vr.AllowList != nil {
  110. if len(vr.Allowlists) > 0 {
  111. return Config{}, fmt.Errorf("%s: [rules.allowlist] is deprecated, it cannot be used alongside [[rules.allowlist]]", rule.RuleID)
  112. }
  113. vr.Allowlists = append(vr.Allowlists, *vr.AllowList)
  114. }
  115. for _, a := range vr.Allowlists {
  116. var condition AllowlistMatchCondition
  117. c := strings.ToUpper(a.Condition)
  118. switch c {
  119. case "AND", "&&":
  120. condition = AllowlistMatchAnd
  121. case "", "OR", "||":
  122. condition = AllowlistMatchOr
  123. default:
  124. return Config{}, fmt.Errorf("%s: unknown allowlist condition '%s' (expected 'and', 'or')", rule.RuleID, c)
  125. }
  126. // Validate the target.
  127. if a.RegexTarget != "" {
  128. switch a.RegexTarget {
  129. case "secret":
  130. a.RegexTarget = ""
  131. case "match", "line":
  132. // do nothing
  133. default:
  134. return Config{}, fmt.Errorf("%s: unknown allowlist |regexTarget| '%s' (expected 'match', 'line')", rule.RuleID, a.RegexTarget)
  135. }
  136. }
  137. var allowlistRegexes []*regexp.Regexp
  138. for _, a := range a.Regexes {
  139. allowlistRegexes = append(allowlistRegexes, regexp.MustCompile(a))
  140. }
  141. var allowlistPaths []*regexp.Regexp
  142. for _, a := range a.Paths {
  143. allowlistPaths = append(allowlistPaths, regexp.MustCompile(a))
  144. }
  145. allowlist := Allowlist{
  146. MatchCondition: condition,
  147. RegexTarget: a.RegexTarget,
  148. Regexes: allowlistRegexes,
  149. Paths: allowlistPaths,
  150. Commits: a.Commits,
  151. StopWords: a.StopWords,
  152. }
  153. if err := allowlist.Validate(); err != nil {
  154. return Config{}, fmt.Errorf("%s: %w", rule.RuleID, err)
  155. }
  156. rule.Allowlists = append(rule.Allowlists, allowlist)
  157. }
  158. orderedRules = append(orderedRules, rule.RuleID)
  159. rulesMap[rule.RuleID] = rule
  160. }
  161. var allowlistRegexes []*regexp.Regexp
  162. for _, a := range vc.Allowlist.Regexes {
  163. allowlistRegexes = append(allowlistRegexes, regexp.MustCompile(a))
  164. }
  165. var allowlistPaths []*regexp.Regexp
  166. for _, a := range vc.Allowlist.Paths {
  167. allowlistPaths = append(allowlistPaths, regexp.MustCompile(a))
  168. }
  169. c := Config{
  170. Description: vc.Description,
  171. Extend: vc.Extend,
  172. Rules: rulesMap,
  173. Allowlist: Allowlist{
  174. RegexTarget: vc.Allowlist.RegexTarget,
  175. Regexes: allowlistRegexes,
  176. Paths: allowlistPaths,
  177. Commits: vc.Allowlist.Commits,
  178. StopWords: vc.Allowlist.StopWords,
  179. },
  180. Keywords: keywords,
  181. OrderedRules: orderedRules,
  182. }
  183. if maxExtendDepth != extendDepth {
  184. // disallow both usedefault and path from being set
  185. if c.Extend.Path != "" && c.Extend.UseDefault {
  186. log.Fatal().Msg("unable to load config due to extend.path and extend.useDefault being set")
  187. }
  188. if c.Extend.UseDefault {
  189. c.extendDefault()
  190. } else if c.Extend.Path != "" {
  191. c.extendPath()
  192. }
  193. }
  194. // Validate the rules after everything has been assembled (including extended configs).
  195. if extendDepth == 0 {
  196. for _, rule := range rulesMap {
  197. if err := rule.Validate(); err != nil {
  198. return Config{}, err
  199. }
  200. }
  201. }
  202. return c, nil
  203. }
  204. func (c *Config) GetOrderedRules() []Rule {
  205. var orderedRules []Rule
  206. for _, id := range c.OrderedRules {
  207. if _, ok := c.Rules[id]; ok {
  208. orderedRules = append(orderedRules, c.Rules[id])
  209. }
  210. }
  211. return orderedRules
  212. }
  213. func (c *Config) extendDefault() {
  214. extendDepth++
  215. viper.SetConfigType("toml")
  216. if err := viper.ReadConfig(strings.NewReader(DefaultConfig)); err != nil {
  217. log.Fatal().Msgf("failed to load extended config, err: %s", err)
  218. return
  219. }
  220. defaultViperConfig := ViperConfig{}
  221. if err := viper.Unmarshal(&defaultViperConfig); err != nil {
  222. log.Fatal().Msgf("failed to load extended config, err: %s", err)
  223. return
  224. }
  225. cfg, err := defaultViperConfig.Translate()
  226. if err != nil {
  227. log.Fatal().Msgf("failed to load extended config, err: %s", err)
  228. return
  229. }
  230. log.Debug().Msg("extending config with default config")
  231. c.extend(cfg)
  232. }
  233. func (c *Config) extendPath() {
  234. extendDepth++
  235. viper.SetConfigFile(c.Extend.Path)
  236. if err := viper.ReadInConfig(); err != nil {
  237. log.Fatal().Msgf("failed to load extended config, err: %s", err)
  238. return
  239. }
  240. extensionViperConfig := ViperConfig{}
  241. if err := viper.Unmarshal(&extensionViperConfig); err != nil {
  242. log.Fatal().Msgf("failed to load extended config, err: %s", err)
  243. return
  244. }
  245. cfg, err := extensionViperConfig.Translate()
  246. if err != nil {
  247. log.Fatal().Msgf("failed to load extended config, err: %s", err)
  248. return
  249. }
  250. log.Debug().Msgf("extending config with %s", c.Extend.Path)
  251. c.extend(cfg)
  252. }
  253. func (c *Config) extendURL() {
  254. // TODO
  255. }
  256. func (c *Config) extend(extensionConfig Config) {
  257. for ruleID, baseRule := range extensionConfig.Rules {
  258. currentRule, ok := c.Rules[ruleID]
  259. if !ok {
  260. // Rule doesn't exist, add it to the config.
  261. c.Rules[ruleID] = baseRule
  262. for _, k := range baseRule.Keywords {
  263. c.Keywords[k] = struct{}{}
  264. }
  265. c.OrderedRules = append(c.OrderedRules, ruleID)
  266. } else {
  267. // Rule exists, merge our changes into the base.
  268. if currentRule.Description != "" {
  269. baseRule.Description = currentRule.Description
  270. }
  271. if currentRule.Entropy != 0 {
  272. baseRule.Entropy = currentRule.Entropy
  273. }
  274. if currentRule.SecretGroup != 0 {
  275. baseRule.SecretGroup = currentRule.SecretGroup
  276. }
  277. if currentRule.Regex != nil {
  278. baseRule.Regex = currentRule.Regex
  279. }
  280. if currentRule.Path != nil {
  281. baseRule.Path = currentRule.Path
  282. }
  283. baseRule.Tags = append(baseRule.Tags, currentRule.Tags...)
  284. baseRule.Keywords = append(baseRule.Keywords, currentRule.Keywords...)
  285. for _, a := range currentRule.Allowlists {
  286. baseRule.Allowlists = append(baseRule.Allowlists, a)
  287. }
  288. // The keywords from the base rule and the extended rule must be merged into the global keywords list
  289. for _, k := range baseRule.Keywords {
  290. c.Keywords[k] = struct{}{}
  291. }
  292. for _, k := range currentRule.Keywords {
  293. c.Keywords[k] = struct{}{}
  294. }
  295. c.Rules[ruleID] = baseRule
  296. }
  297. }
  298. // append allowlists, not attempting to merge
  299. c.Allowlist.Commits = append(c.Allowlist.Commits,
  300. extensionConfig.Allowlist.Commits...)
  301. c.Allowlist.Paths = append(c.Allowlist.Paths,
  302. extensionConfig.Allowlist.Paths...)
  303. c.Allowlist.Regexes = append(c.Allowlist.Regexes,
  304. extensionConfig.Allowlist.Regexes...)
  305. // sort to keep extended rules in order
  306. sort.Strings(c.OrderedRules)
  307. }