gitleaks_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650
  1. package main
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "os"
  6. "path"
  7. "regexp"
  8. "strings"
  9. "testing"
  10. "github.com/franela/goblin"
  11. git "gopkg.in/src-d/go-git.v4"
  12. "gopkg.in/src-d/go-git.v4/storage/memory"
  13. )
  14. const testWhitelistCommit = `
  15. [[regexes]]
  16. description = "AWS"
  17. regex = '''AKIA[0-9A-Z]{16}'''
  18. [whitelist]
  19. commits = [
  20. "eaeffdc65b4c73ccb67e75d96bd8743be2c85973",
  21. ]
  22. `
  23. const testWhitelistFile = `
  24. [[regexes]]
  25. description = "AWS"
  26. regex = '''AKIA[0-9A-Z]{16}'''
  27. [whitelist]
  28. files = [
  29. ".go",
  30. ]
  31. `
  32. const testWhitelistBranch = `
  33. [[regexes]]
  34. description = "AWS"
  35. regex = '''AKIA[0-9A-Z]{16}'''
  36. [whitelist]
  37. branches = [
  38. "origin/master",
  39. ]
  40. `
  41. const testWhitelistRegex = `
  42. [[regexes]]
  43. description = "AWS"
  44. regex = '''AKIA[0-9A-Z]{16}'''
  45. [whitelist]
  46. regexes= [
  47. "AKIA",
  48. ]
  49. `
  50. func TestGetRepo(t *testing.T) {
  51. var err error
  52. dir, err = ioutil.TempDir("", "gitleaksTestRepo")
  53. defer os.RemoveAll(dir)
  54. if err != nil {
  55. panic(err)
  56. }
  57. _, err = git.PlainClone(dir, false, &git.CloneOptions{
  58. URL: "https://github.com/gitleakstest/gronit",
  59. })
  60. if err != nil {
  61. panic(err)
  62. }
  63. var tests = []struct {
  64. testOpts Options
  65. description string
  66. expectedErrMsg string
  67. }{
  68. {
  69. testOpts: Options{
  70. Repo: "https://github.com/gitleakstest/gronit",
  71. },
  72. description: "test plain clone remote repo",
  73. expectedErrMsg: "",
  74. },
  75. {
  76. testOpts: Options{
  77. Repo: "https://github.com/gitleakstest/gronit",
  78. Disk: true,
  79. },
  80. description: "test on disk clone remote repo",
  81. expectedErrMsg: "",
  82. },
  83. {
  84. testOpts: Options{
  85. RepoPath: dir,
  86. },
  87. description: "test local clone repo",
  88. expectedErrMsg: "",
  89. },
  90. {
  91. testOpts: Options{
  92. Repo: "https://github.com/gitleakstest/nope",
  93. },
  94. description: "test no repo",
  95. expectedErrMsg: "authentication required",
  96. },
  97. {
  98. testOpts: Options{
  99. Repo: "https://github.com/gitleakstest/private",
  100. IncludePrivate: true,
  101. },
  102. description: "test private repo",
  103. expectedErrMsg: "invalid auth method",
  104. },
  105. {
  106. testOpts: Options{
  107. Repo: "https://github.com/gitleakstest/private",
  108. IncludePrivate: true,
  109. Disk: true,
  110. },
  111. description: "test private repo",
  112. expectedErrMsg: "invalid auth method",
  113. },
  114. }
  115. g := goblin.Goblin(t)
  116. for _, test := range tests {
  117. g.Describe("TestGetRepo", func() {
  118. g.It(test.description, func() {
  119. opts = test.testOpts
  120. _, err := getRepo()
  121. if err != nil {
  122. g.Assert(err.Error()).Equal(test.expectedErrMsg)
  123. }
  124. })
  125. })
  126. }
  127. }
  128. func TestGetOwnerRepo(t *testing.T) {
  129. var err error
  130. dir, err = ioutil.TempDir("", "gitleaksTestOwner")
  131. defer os.RemoveAll(dir)
  132. if err != nil {
  133. panic(err)
  134. }
  135. git.PlainClone(dir+"/gronit", false, &git.CloneOptions{
  136. URL: "https://github.com/gitleakstest/gronit",
  137. })
  138. git.PlainClone(dir+"/h1domains", false, &git.CloneOptions{
  139. URL: "https://github.com/gitleakstest/h1domains",
  140. })
  141. var tests = []struct {
  142. testOpts Options
  143. description string
  144. expectedErrMsg string
  145. numRepos int
  146. }{
  147. {
  148. testOpts: Options{
  149. GithubUser: "gitleakstest",
  150. },
  151. description: "test github user",
  152. numRepos: 2,
  153. expectedErrMsg: "",
  154. },
  155. {
  156. testOpts: Options{
  157. GithubUser: "gitleakstest",
  158. Disk: true,
  159. },
  160. description: "test github user on disk ",
  161. numRepos: 2,
  162. expectedErrMsg: "",
  163. },
  164. {
  165. testOpts: Options{
  166. GithubOrg: "gitleakstestorg",
  167. },
  168. description: "test github org",
  169. numRepos: 2,
  170. expectedErrMsg: "",
  171. },
  172. {
  173. testOpts: Options{
  174. OwnerPath: dir,
  175. },
  176. description: "test plain clone remote repo",
  177. numRepos: 2,
  178. expectedErrMsg: "",
  179. },
  180. {
  181. testOpts: Options{
  182. GithubOrg: "gitleakstestorg",
  183. IncludePrivate: true,
  184. },
  185. description: "test private org no ssh",
  186. numRepos: 0,
  187. expectedErrMsg: "no ssh auth available",
  188. },
  189. {
  190. testOpts: Options{
  191. GithubOrg: "gitleakstestorg",
  192. Disk: true,
  193. },
  194. description: "test org on disk",
  195. numRepos: 2,
  196. expectedErrMsg: "",
  197. },
  198. {
  199. testOpts: Options{
  200. GithubOrg: "gitleakstestorg",
  201. IncludePrivate: true,
  202. Disk: true,
  203. },
  204. description: "test private org on disk no ssh",
  205. numRepos: 0,
  206. expectedErrMsg: "no ssh auth available",
  207. },
  208. }
  209. g := goblin.Goblin(t)
  210. for _, test := range tests {
  211. g.Describe("TestGetOwnerRepo", func() {
  212. g.It(test.description, func() {
  213. opts = test.testOpts
  214. repos, err := getOwnerRepos()
  215. if err != nil {
  216. g.Assert(err.Error()).Equal(test.expectedErrMsg)
  217. }
  218. g.Assert(len(repos)).Equal(test.numRepos)
  219. })
  220. })
  221. }
  222. }
  223. func TestWriteReport(t *testing.T) {
  224. tmpDir, _ := ioutil.TempDir("", "reportDir")
  225. reportFile := path.Join(tmpDir, "report.json")
  226. defer os.RemoveAll(tmpDir)
  227. leaks := []Leak{
  228. {
  229. Line: "eat",
  230. Commit: "your",
  231. Offender: "veggies",
  232. Type: "and",
  233. Message: "get",
  234. Author: "some",
  235. File: "sleep",
  236. Branch: "thxu",
  237. },
  238. }
  239. var tests = []struct {
  240. leaks []Leak
  241. reportFile string
  242. fileName string
  243. description string
  244. testOpts Options
  245. }{
  246. {
  247. leaks: leaks,
  248. reportFile: reportFile,
  249. fileName: "report.json",
  250. description: "can we write a file",
  251. testOpts: Options{
  252. Report: reportFile,
  253. },
  254. },
  255. }
  256. g := goblin.Goblin(t)
  257. for _, test := range tests {
  258. g.Describe("TestWriteReport", func() {
  259. g.It(test.description, func() {
  260. opts = test.testOpts
  261. writeReport(test.leaks)
  262. f, _ := os.Stat(test.reportFile)
  263. g.Assert(f.Name()).Equal(test.fileName)
  264. })
  265. })
  266. }
  267. }
  268. func testTomlLoader() string {
  269. tmpDir, _ := ioutil.TempDir("", "whiteListConfigs")
  270. ioutil.WriteFile(path.Join(tmpDir, "regex"), []byte(testWhitelistRegex), 0644)
  271. ioutil.WriteFile(path.Join(tmpDir, "branch"), []byte(testWhitelistBranch), 0644)
  272. ioutil.WriteFile(path.Join(tmpDir, "commit"), []byte(testWhitelistCommit), 0644)
  273. ioutil.WriteFile(path.Join(tmpDir, "file"), []byte(testWhitelistFile), 0644)
  274. return tmpDir
  275. }
  276. func TestAuditRepo(t *testing.T) {
  277. var leaks []Leak
  278. err := loadToml()
  279. configsDir := testTomlLoader()
  280. defer os.RemoveAll(configsDir)
  281. if err != nil {
  282. panic(err)
  283. }
  284. leaksRepo, err := git.Clone(memory.NewStorage(), nil, &git.CloneOptions{
  285. URL: "https://github.com/gitleakstest/gronit.git",
  286. })
  287. if err != nil {
  288. panic(err)
  289. }
  290. cleanRepo, err := git.Clone(memory.NewStorage(), nil, &git.CloneOptions{
  291. URL: "https://github.com/gitleakstest/h1domains.git",
  292. })
  293. if err != nil {
  294. panic(err)
  295. }
  296. var tests = []struct {
  297. testOpts Options
  298. description string
  299. expectedErrMsg string
  300. numLeaks int
  301. repo *git.Repository
  302. whiteListFiles []*regexp.Regexp
  303. whiteListCommits map[string]bool
  304. whiteListBranches []string
  305. whiteListRegexes []*regexp.Regexp
  306. configPath string
  307. }{
  308. {
  309. repo: leaksRepo,
  310. description: "two leaks present",
  311. numLeaks: 2,
  312. },
  313. {
  314. repo: leaksRepo,
  315. description: "two leaks present limit goroutines",
  316. numLeaks: 2,
  317. testOpts: Options{
  318. MaxGoRoutines: 2,
  319. },
  320. },
  321. {
  322. repo: leaksRepo,
  323. description: "audit all branch",
  324. numLeaks: 6,
  325. testOpts: Options{
  326. AuditAllRefs: true,
  327. },
  328. },
  329. {
  330. repo: leaksRepo,
  331. description: "audit all branch whitelist 1",
  332. numLeaks: 4,
  333. testOpts: Options{
  334. AuditAllRefs: true,
  335. },
  336. whiteListBranches: []string{
  337. "origin/master",
  338. },
  339. },
  340. {
  341. repo: leaksRepo,
  342. description: "two leaks present whitelist AWS.. no leaks",
  343. whiteListRegexes: []*regexp.Regexp{
  344. regexp.MustCompile("AKIA"),
  345. },
  346. numLeaks: 0,
  347. },
  348. {
  349. repo: leaksRepo,
  350. description: "two leaks present limit goroutines",
  351. numLeaks: 2,
  352. },
  353. {
  354. repo: cleanRepo,
  355. description: "no leaks present",
  356. numLeaks: 0,
  357. },
  358. {
  359. repo: leaksRepo,
  360. description: "two leaks present whitelist go files",
  361. whiteListFiles: []*regexp.Regexp{
  362. regexp.MustCompile(".go"),
  363. },
  364. numLeaks: 0,
  365. },
  366. {
  367. repo: leaksRepo,
  368. description: "two leaks present whitelist bad commit",
  369. whiteListCommits: map[string]bool{
  370. "eaeffdc65b4c73ccb67e75d96bd8743be2c85973": true,
  371. },
  372. numLeaks: 1,
  373. },
  374. {
  375. repo: leaksRepo,
  376. description: "redact",
  377. testOpts: Options{
  378. Redact: true,
  379. },
  380. numLeaks: 2,
  381. },
  382. {
  383. repo: leaksRepo,
  384. description: "toml whitelist regex",
  385. configPath: path.Join(configsDir, "regex"),
  386. numLeaks: 0,
  387. },
  388. {
  389. repo: leaksRepo,
  390. description: "toml whitelist branch",
  391. configPath: path.Join(configsDir, "branch"),
  392. testOpts: Options{
  393. AuditAllRefs: true,
  394. },
  395. numLeaks: 4,
  396. },
  397. {
  398. repo: leaksRepo,
  399. description: "toml whitelist file",
  400. configPath: path.Join(configsDir, "file"),
  401. numLeaks: 0,
  402. },
  403. {
  404. repo: leaksRepo,
  405. description: "toml whitelist commit",
  406. configPath: path.Join(configsDir, "commit"),
  407. numLeaks: 1,
  408. },
  409. }
  410. whiteListCommits = make(map[string]bool)
  411. g := goblin.Goblin(t)
  412. for _, test := range tests {
  413. g.Describe("TestAuditRepo", func() {
  414. g.It(test.description, func() {
  415. opts = test.testOpts
  416. // settin da globs
  417. if test.whiteListFiles != nil {
  418. whiteListFiles = test.whiteListFiles
  419. } else {
  420. whiteListFiles = nil
  421. }
  422. if test.whiteListCommits != nil {
  423. whiteListCommits = test.whiteListCommits
  424. } else {
  425. whiteListCommits = nil
  426. }
  427. if test.whiteListBranches != nil {
  428. whiteListBranches = test.whiteListBranches
  429. } else {
  430. whiteListBranches = nil
  431. }
  432. if test.whiteListRegexes != nil {
  433. whiteListRegexes = test.whiteListRegexes
  434. } else {
  435. whiteListRegexes = nil
  436. }
  437. // config paths
  438. if test.configPath != "" {
  439. os.Setenv("GITLEAKS_CONFIG", test.configPath)
  440. loadToml()
  441. }
  442. leaks, err = auditRepo(test.repo)
  443. if opts.Redact {
  444. g.Assert(leaks[0].Offender).Equal("REDACTED")
  445. }
  446. g.Assert(len(leaks)).Equal(test.numLeaks)
  447. })
  448. })
  449. }
  450. }
  451. func TestOptionGuard(t *testing.T) {
  452. var tests = []struct {
  453. testOpts Options
  454. githubToken bool
  455. description string
  456. expectedErrMsg string
  457. expectedErrMsgFuzzy string
  458. }{
  459. {
  460. testOpts: Options{},
  461. description: "default no opts",
  462. expectedErrMsg: "",
  463. },
  464. {
  465. testOpts: Options{
  466. IncludePrivate: true,
  467. GithubOrg: "fakeOrg",
  468. },
  469. description: "private org no githubtoken",
  470. expectedErrMsg: "user/organization private repos require env var GITHUB_TOKEN to be set",
  471. githubToken: false,
  472. },
  473. {
  474. testOpts: Options{
  475. IncludePrivate: true,
  476. GithubUser: "fakeUser",
  477. },
  478. description: "private user no githubtoken",
  479. expectedErrMsg: "user/organization private repos require env var GITHUB_TOKEN to be set",
  480. githubToken: false,
  481. },
  482. {
  483. testOpts: Options{
  484. IncludePrivate: true,
  485. GithubUser: "fakeUser",
  486. GithubOrg: "fakeOrg",
  487. },
  488. description: "double owner",
  489. expectedErrMsg: "github user and organization set",
  490. },
  491. {
  492. testOpts: Options{
  493. IncludePrivate: true,
  494. GithubOrg: "fakeOrg",
  495. OwnerPath: "/dev/null",
  496. },
  497. description: "local and remote target",
  498. expectedErrMsg: "github organization set and local owner path",
  499. },
  500. {
  501. testOpts: Options{
  502. IncludePrivate: true,
  503. GithubUser: "fakeUser",
  504. OwnerPath: "/dev/null",
  505. },
  506. description: "local and remote target",
  507. expectedErrMsg: "github user set and local owner path",
  508. },
  509. {
  510. testOpts: Options{
  511. GithubUser: "fakeUser",
  512. SingleSearch: "*/./....",
  513. },
  514. description: "single search invalid regex gaurd",
  515. expectedErrMsgFuzzy: "unable to compile regex: */./...., ",
  516. },
  517. {
  518. testOpts: Options{
  519. GithubUser: "fakeUser",
  520. SingleSearch: "mystring",
  521. },
  522. description: "single search regex gaurd",
  523. expectedErrMsg: "",
  524. },
  525. }
  526. g := goblin.Goblin(t)
  527. for _, test := range tests {
  528. g.Describe("Test Option Gaurd", func() {
  529. g.It(test.description, func() {
  530. os.Clearenv()
  531. opts = test.testOpts
  532. if test.githubToken {
  533. os.Setenv("GITHUB_TOKEN", "fakeToken")
  534. }
  535. err := optsGuard()
  536. if err != nil {
  537. if test.expectedErrMsgFuzzy != "" {
  538. g.Assert(strings.Contains(err.Error(), test.expectedErrMsgFuzzy)).Equal(true)
  539. } else {
  540. g.Assert(err.Error()).Equal(test.expectedErrMsg)
  541. }
  542. } else {
  543. g.Assert("").Equal(test.expectedErrMsg)
  544. }
  545. })
  546. })
  547. }
  548. }
  549. func TestLoadToml(t *testing.T) {
  550. tmpDir, _ := ioutil.TempDir("", "gitleaksTestConfigDir")
  551. defer os.RemoveAll(tmpDir)
  552. err := ioutil.WriteFile(path.Join(tmpDir, "gitleaksConfig"), []byte(defaultConfig), 0644)
  553. if err != nil {
  554. panic(err)
  555. }
  556. configPath := path.Join(tmpDir, "gitleaksConfig")
  557. noConfigPath := path.Join(tmpDir, "gitleaksConfigNope")
  558. var tests = []struct {
  559. testOpts Options
  560. description string
  561. configPath string
  562. expectedErrMsg string
  563. singleSearch bool
  564. }{
  565. {
  566. testOpts: Options{
  567. ConfigPath: configPath,
  568. },
  569. description: "path to config",
  570. },
  571. {
  572. testOpts: Options{},
  573. description: "env var path to no config",
  574. singleSearch: true,
  575. },
  576. {
  577. testOpts: Options{
  578. ConfigPath: noConfigPath,
  579. },
  580. description: "no path to config",
  581. expectedErrMsg: fmt.Sprintf("no gitleaks config at %s", noConfigPath),
  582. },
  583. {
  584. testOpts: Options{},
  585. description: "env var path to config",
  586. configPath: configPath,
  587. expectedErrMsg: "",
  588. },
  589. {
  590. testOpts: Options{},
  591. description: "env var path to no config",
  592. configPath: noConfigPath,
  593. expectedErrMsg: fmt.Sprintf("problem loading config: open %s: no such file or directory", noConfigPath),
  594. },
  595. }
  596. g := goblin.Goblin(t)
  597. for _, test := range tests {
  598. g.Describe("TestLoadToml", func() {
  599. g.It(test.description, func() {
  600. opts = test.testOpts
  601. if test.singleSearch {
  602. singleSearchRegex = regexp.MustCompile("test")
  603. } else {
  604. singleSearchRegex = nil
  605. }
  606. if test.configPath != "" {
  607. os.Setenv("GITLEAKS_CONFIG", test.configPath)
  608. } else {
  609. os.Clearenv()
  610. }
  611. err := loadToml()
  612. if err != nil {
  613. g.Assert(err.Error()).Equal(test.expectedErrMsg)
  614. } else {
  615. g.Assert("").Equal(test.expectedErrMsg)
  616. }
  617. })
  618. })
  619. }
  620. }