config_test.go 12 KB

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