config_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. package config
  2. import (
  3. "fmt"
  4. "testing"
  5. "github.com/google/go-cmp/cmp"
  6. regexp "github.com/wasilibs/go-re2"
  7. "github.com/spf13/viper"
  8. "github.com/stretchr/testify/assert"
  9. "github.com/stretchr/testify/require"
  10. )
  11. const configPath = "../testdata/config/"
  12. func TestTranslate(t *testing.T) {
  13. tests := []struct {
  14. // Configuration file basename to load, from `../testdata/config/`.
  15. cfgName string
  16. // Expected result.
  17. cfg Config
  18. // Rules to compare.
  19. rules []string
  20. // Error to expect.
  21. wantError error
  22. }{
  23. {
  24. cfgName: "allowlist_old_compat",
  25. cfg: Config{
  26. Rules: map[string]Rule{"example": {
  27. RuleID: "example",
  28. Regex: regexp.MustCompile(`example\d+`),
  29. Tags: []string{},
  30. Keywords: []string{},
  31. Allowlists: []Allowlist{
  32. {
  33. MatchCondition: AllowlistMatchOr,
  34. Regexes: []*regexp.Regexp{regexp.MustCompile("123")},
  35. },
  36. },
  37. },
  38. },
  39. },
  40. },
  41. {
  42. cfgName: "allowlist_invalid_empty",
  43. cfg: Config{},
  44. wantError: fmt.Errorf("example: [[rules.allowlists]] must contain at least one check for: commits, paths, regexes, or stopwords"),
  45. },
  46. {
  47. cfgName: "allowlist_invalid_old_and_new",
  48. cfg: Config{},
  49. wantError: fmt.Errorf("example: [rules.allowlist] is deprecated, it cannot be used alongside [[rules.allowlist]]"),
  50. },
  51. {
  52. cfgName: "allowlist_invalid_regextarget",
  53. cfg: Config{},
  54. wantError: fmt.Errorf("example: unknown allowlist |regexTarget| 'mtach' (expected 'match', 'line')"),
  55. },
  56. {
  57. cfgName: "allow_aws_re",
  58. cfg: Config{
  59. Rules: map[string]Rule{"aws-access-key": {
  60. RuleID: "aws-access-key",
  61. Description: "AWS Access Key",
  62. Regex: regexp.MustCompile("(?:A3T[A-Z0-9]|AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}"),
  63. Keywords: []string{},
  64. Tags: []string{"key", "AWS"},
  65. Allowlists: []Allowlist{
  66. {
  67. MatchCondition: AllowlistMatchOr,
  68. Regexes: []*regexp.Regexp{regexp.MustCompile("AKIALALEMEL33243OLIA")},
  69. },
  70. },
  71. },
  72. },
  73. },
  74. },
  75. {
  76. cfgName: "allow_commit",
  77. cfg: Config{
  78. Rules: map[string]Rule{"aws-access-key": {
  79. RuleID: "aws-access-key",
  80. Description: "AWS Access Key",
  81. Regex: regexp.MustCompile("(?:A3T[A-Z0-9]|AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}"),
  82. Keywords: []string{},
  83. Tags: []string{"key", "AWS"},
  84. Allowlists: []Allowlist{
  85. {
  86. MatchCondition: AllowlistMatchOr,
  87. Commits: []string{"allowthiscommit"},
  88. },
  89. },
  90. },
  91. },
  92. },
  93. },
  94. {
  95. cfgName: "allow_path",
  96. cfg: Config{
  97. Rules: map[string]Rule{"aws-access-key": {
  98. RuleID: "aws-access-key",
  99. Description: "AWS Access Key",
  100. Regex: regexp.MustCompile("(?:A3T[A-Z0-9]|AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}"),
  101. Keywords: []string{},
  102. Tags: []string{"key", "AWS"},
  103. Allowlists: []Allowlist{
  104. {
  105. MatchCondition: AllowlistMatchOr,
  106. Paths: []*regexp.Regexp{regexp.MustCompile(".go")},
  107. },
  108. },
  109. },
  110. },
  111. },
  112. },
  113. {
  114. cfgName: "entropy_group",
  115. cfg: Config{
  116. Rules: map[string]Rule{"discord-api-key": {
  117. RuleID: "discord-api-key",
  118. Description: "Discord API key",
  119. Regex: regexp.MustCompile(`(?i)(discord[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-h0-9]{64})['\"]`),
  120. Keywords: []string{},
  121. Entropy: 3.5,
  122. SecretGroup: 3,
  123. Tags: []string{},
  124. },
  125. },
  126. },
  127. },
  128. {
  129. cfgName: "missing_id",
  130. cfg: Config{},
  131. wantError: fmt.Errorf("rule |id| is missing or empty, regex: (?i)(discord[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([a-h0-9]{64})['\\\"]"),
  132. },
  133. {
  134. cfgName: "no_regex_or_path",
  135. cfg: Config{},
  136. wantError: fmt.Errorf("discord-api-key: both |regex| and |path| are empty, this rule will have no effect"),
  137. },
  138. {
  139. cfgName: "bad_entropy_group",
  140. cfg: Config{},
  141. wantError: fmt.Errorf("discord-api-key: invalid regex secret group 5, max regex secret group 3"),
  142. },
  143. {
  144. cfgName: "base",
  145. cfg: Config{
  146. Rules: map[string]Rule{
  147. "aws-access-key": {
  148. RuleID: "aws-access-key",
  149. Description: "AWS Access Key",
  150. Regex: regexp.MustCompile("(?:A3T[A-Z0-9]|AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}"),
  151. Keywords: []string{},
  152. Tags: []string{"key", "AWS"},
  153. },
  154. "aws-secret-key": {
  155. RuleID: "aws-secret-key",
  156. Description: "AWS Secret Key",
  157. Regex: regexp.MustCompile(`(?i)aws_(.{0,20})?=?.[\'\"0-9a-zA-Z\/+]{40}`),
  158. Keywords: []string{},
  159. Tags: []string{"key", "AWS"},
  160. },
  161. "aws-secret-key-again": {
  162. RuleID: "aws-secret-key-again",
  163. Description: "AWS Secret Key",
  164. Regex: regexp.MustCompile(`(?i)aws_(.{0,20})?=?.[\'\"0-9a-zA-Z\/+]{40}`),
  165. Keywords: []string{},
  166. Tags: []string{"key", "AWS"},
  167. },
  168. },
  169. },
  170. },
  171. {
  172. cfgName: "extend_rule_allowlist_or",
  173. cfg: Config{
  174. Rules: map[string]Rule{
  175. "aws-secret-key-again-again": {
  176. RuleID: "aws-secret-key-again-again",
  177. Description: "AWS Secret Key",
  178. Regex: regexp.MustCompile(`(?i)aws_(.{0,20})?=?.[\'\"0-9a-zA-Z\/+]{40}`),
  179. Keywords: []string{},
  180. Tags: []string{"key", "AWS"},
  181. Allowlists: []Allowlist{
  182. {
  183. MatchCondition: AllowlistMatchOr,
  184. StopWords: []string{"fake"},
  185. },
  186. {
  187. MatchCondition: AllowlistMatchOr,
  188. Commits: []string{"abcdefg1"},
  189. Paths: []*regexp.Regexp{regexp.MustCompile(`ignore\.xaml`)},
  190. Regexes: []*regexp.Regexp{regexp.MustCompile(`foo.+bar`)},
  191. RegexTarget: "line",
  192. StopWords: []string{"example"},
  193. },
  194. },
  195. },
  196. },
  197. },
  198. },
  199. {
  200. cfgName: "extend_rule_allowlist_and",
  201. cfg: Config{
  202. Rules: map[string]Rule{
  203. "aws-secret-key-again-again": {
  204. RuleID: "aws-secret-key-again-again",
  205. Description: "AWS Secret Key",
  206. Regex: regexp.MustCompile(`(?i)aws_(.{0,20})?=?.[\'\"0-9a-zA-Z\/+]{40}`),
  207. Keywords: []string{},
  208. Tags: []string{"key", "AWS"},
  209. Allowlists: []Allowlist{
  210. {
  211. MatchCondition: AllowlistMatchOr,
  212. StopWords: []string{"fake"},
  213. },
  214. {
  215. MatchCondition: AllowlistMatchAnd,
  216. Commits: []string{"abcdefg1"},
  217. Paths: []*regexp.Regexp{regexp.MustCompile(`ignore\.xaml`)},
  218. Regexes: []*regexp.Regexp{regexp.MustCompile(`foo.+bar`)},
  219. RegexTarget: "line",
  220. StopWords: []string{"example"},
  221. },
  222. },
  223. },
  224. },
  225. },
  226. },
  227. {
  228. cfgName: "extend_empty_regexpath",
  229. cfg: Config{
  230. Rules: map[string]Rule{
  231. "aws-secret-key-again-again": {
  232. RuleID: "aws-secret-key-again-again",
  233. Description: "AWS Secret Key",
  234. Regex: regexp.MustCompile(`(?i)aws_(.{0,20})?=?.[\'\"0-9a-zA-Z\/+]{40}`),
  235. Keywords: []string{},
  236. Tags: []string{"key", "AWS"},
  237. Allowlists: []Allowlist{
  238. {
  239. MatchCondition: AllowlistMatchOr,
  240. Paths: []*regexp.Regexp{regexp.MustCompile(`something.py`)},
  241. },
  242. },
  243. },
  244. },
  245. },
  246. },
  247. {
  248. cfgName: "override_description",
  249. rules: []string{"aws-access-key"},
  250. cfg: Config{
  251. Rules: map[string]Rule{"aws-access-key": {
  252. RuleID: "aws-access-key",
  253. Description: "Puppy Doggy",
  254. Regex: regexp.MustCompile("(?:A3T[A-Z0-9]|AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}"),
  255. Keywords: []string{},
  256. Tags: []string{"key", "AWS"},
  257. },
  258. },
  259. },
  260. },
  261. {
  262. cfgName: "override_entropy",
  263. rules: []string{"aws-access-key"},
  264. cfg: Config{
  265. Rules: map[string]Rule{"aws-access-key": {
  266. RuleID: "aws-access-key",
  267. Description: "AWS Access Key",
  268. Regex: regexp.MustCompile("(?:A3T[A-Z0-9]|AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}"),
  269. Entropy: 999.0,
  270. Keywords: []string{},
  271. Tags: []string{"key", "AWS"},
  272. },
  273. },
  274. },
  275. },
  276. {
  277. cfgName: "override_secret_group",
  278. rules: []string{"aws-access-key"},
  279. cfg: Config{
  280. Rules: map[string]Rule{"aws-access-key": {
  281. RuleID: "aws-access-key",
  282. Description: "AWS Access Key",
  283. Regex: regexp.MustCompile("(?:a)(?:a)"),
  284. SecretGroup: 2,
  285. Keywords: []string{},
  286. Tags: []string{"key", "AWS"},
  287. },
  288. },
  289. },
  290. },
  291. {
  292. cfgName: "override_regex",
  293. rules: []string{"aws-access-key"},
  294. cfg: Config{
  295. Rules: map[string]Rule{"aws-access-key": {
  296. RuleID: "aws-access-key",
  297. Description: "AWS Access Key",
  298. Regex: regexp.MustCompile("(?:a)"),
  299. Keywords: []string{},
  300. Tags: []string{"key", "AWS"},
  301. },
  302. },
  303. },
  304. },
  305. {
  306. cfgName: "override_path",
  307. rules: []string{"aws-access-key"},
  308. cfg: Config{
  309. Rules: map[string]Rule{"aws-access-key": {
  310. RuleID: "aws-access-key",
  311. Description: "AWS Access Key",
  312. Regex: regexp.MustCompile("(?:A3T[A-Z0-9]|AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}"),
  313. Path: regexp.MustCompile("(?:puppy)"),
  314. Keywords: []string{},
  315. Tags: []string{"key", "AWS"},
  316. },
  317. },
  318. },
  319. },
  320. {
  321. cfgName: "override_tags",
  322. rules: []string{"aws-access-key"},
  323. cfg: Config{
  324. Rules: map[string]Rule{"aws-access-key": {
  325. RuleID: "aws-access-key",
  326. Description: "AWS Access Key",
  327. Regex: regexp.MustCompile("(?:A3T[A-Z0-9]|AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}"),
  328. Keywords: []string{},
  329. Tags: []string{"key", "AWS", "puppy"},
  330. },
  331. },
  332. },
  333. },
  334. {
  335. cfgName: "override_keywords",
  336. rules: []string{"aws-access-key"},
  337. cfg: Config{
  338. Rules: map[string]Rule{"aws-access-key": {
  339. RuleID: "aws-access-key",
  340. Description: "AWS Access Key",
  341. Regex: regexp.MustCompile("(?:A3T[A-Z0-9]|AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}"),
  342. Keywords: []string{"puppy"},
  343. Tags: []string{"key", "AWS"},
  344. },
  345. },
  346. },
  347. },
  348. }
  349. for _, tt := range tests {
  350. t.Run(tt.cfgName, func(t *testing.T) {
  351. t.Cleanup(func() {
  352. extendDepth = 0
  353. viper.Reset()
  354. })
  355. viper.AddConfigPath(configPath)
  356. viper.SetConfigName(tt.cfgName)
  357. viper.SetConfigType("toml")
  358. err := viper.ReadInConfig()
  359. require.NoError(t, err)
  360. var vc ViperConfig
  361. err = viper.Unmarshal(&vc)
  362. require.NoError(t, err)
  363. cfg, err := vc.Translate()
  364. if err != nil && !assert.EqualError(t, tt.wantError, err.Error()) {
  365. return
  366. }
  367. if len(tt.rules) > 0 {
  368. rules := make(map[string]Rule)
  369. for _, name := range tt.rules {
  370. rules[name] = cfg.Rules[name]
  371. }
  372. cfg.Rules = rules
  373. }
  374. var regexComparer = func(x, y *regexp.Regexp) bool {
  375. if x == nil || y == nil {
  376. return x == y
  377. }
  378. return x.String() == y.String()
  379. }
  380. opts := cmp.Options{cmp.Comparer(regexComparer)}
  381. if diff := cmp.Diff(tt.cfg.Rules, cfg.Rules, opts); diff != "" {
  382. t.Errorf("%s diff: (-want +got)\n%s", tt.cfgName, diff)
  383. }
  384. })
  385. }
  386. }
  387. func TestExtendedRuleKeywordsAreDowncase(t *testing.T) {
  388. tests := []struct {
  389. name string
  390. cfgName string
  391. expectedKeywords string
  392. }{
  393. {
  394. name: "Extend base rule that includes AWS keyword with new attribute",
  395. cfgName: "extend_base_rule_including_keysword_with_attribute",
  396. expectedKeywords: "aws",
  397. },
  398. {
  399. name: "Extend base with a new rule with CMS keyword",
  400. cfgName: "extend_with_new_rule",
  401. expectedKeywords: "cms",
  402. },
  403. }
  404. for _, tt := range tests {
  405. t.Run(tt.name, func(t *testing.T) {
  406. t.Cleanup(func() {
  407. viper.Reset()
  408. })
  409. viper.AddConfigPath(configPath)
  410. viper.SetConfigName(tt.cfgName)
  411. viper.SetConfigType("toml")
  412. err := viper.ReadInConfig()
  413. require.NoError(t, err)
  414. var vc ViperConfig
  415. err = viper.Unmarshal(&vc)
  416. require.NoError(t, err)
  417. cfg, err := vc.Translate()
  418. require.NoError(t, err)
  419. _, exists := cfg.Keywords[tt.expectedKeywords]
  420. require.Truef(t, exists, "The expected keyword %s did not exist as a key of cfg.Keywords", tt.expectedKeywords)
  421. })
  422. }
  423. }