config.go 9.7 KB

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