gitrob 0.0.6 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +30 -7
  3. data/.rspec +0 -1
  4. data/.rubocop.yml +55 -0
  5. data/.travis.yml +4 -0
  6. data/CHANGELOG.md +42 -0
  7. data/CONTRIBUTING.md +137 -9
  8. data/Gemfile +11 -1
  9. data/Guardfile +42 -0
  10. data/LICENSE.txt +17 -18
  11. data/README.md +79 -29
  12. data/Rakefile +6 -0
  13. data/bin/console +34 -0
  14. data/bin/setup +7 -0
  15. data/db/migrations/001_create_assessments.rb +19 -0
  16. data/db/migrations/002_create_github_access_tokens.rb +11 -0
  17. data/db/migrations/003_create_owners.rb +24 -0
  18. data/db/migrations/004_create_repositories.rb +23 -0
  19. data/db/migrations/005_create_blobs.rb +16 -0
  20. data/db/migrations/006_create_flags.rb +13 -0
  21. data/db/migrations/007_create_comparisons.rb +17 -0
  22. data/db/migrations/008_create_blobs_comparisons.rb +8 -0
  23. data/db/migrations/009_create_comparisons_repositories.rb +8 -0
  24. data/db/migrations/010_create_comparisons_owners.rb +8 -0
  25. data/exe/gitrob +6 -0
  26. data/gitrob.gemspec +25 -18
  27. data/lib/gitrob/blob_observer.rb +103 -0
  28. data/lib/gitrob/cli/command.rb +58 -0
  29. data/lib/gitrob/cli/commands/accept_terms_of_use.rb +61 -0
  30. data/lib/gitrob/cli/commands/analyze/analysis.rb +75 -0
  31. data/lib/gitrob/cli/commands/analyze/gathering.rb +101 -0
  32. data/lib/gitrob/cli/commands/analyze.rb +63 -0
  33. data/lib/gitrob/cli/commands/banner.rb +25 -0
  34. data/lib/gitrob/cli/commands/configure.rb +123 -0
  35. data/lib/gitrob/cli/commands/server.rb +21 -0
  36. data/lib/gitrob/cli/progress_bar.rb +47 -0
  37. data/lib/gitrob/cli.rb +213 -0
  38. data/lib/gitrob/github/client_manager.rb +46 -0
  39. data/lib/gitrob/github/data_manager.rb +121 -0
  40. data/lib/gitrob/jobs/assessment.rb +12 -0
  41. data/lib/gitrob/jobs/comparison.rb +55 -0
  42. data/lib/gitrob/models/assessment.rb +96 -0
  43. data/lib/gitrob/models/blob.rb +50 -0
  44. data/lib/gitrob/models/comparison.rb +15 -0
  45. data/lib/gitrob/models/flag.rb +15 -0
  46. data/lib/gitrob/models/github_access_token.rb +17 -0
  47. data/lib/gitrob/models/owner.rb +23 -0
  48. data/lib/gitrob/models/repository.rb +20 -0
  49. data/lib/gitrob/utils.rb +19 -0
  50. data/lib/gitrob/version.rb +1 -1
  51. data/lib/gitrob/web_app.rb +292 -0
  52. data/lib/gitrob.rb +30 -113
  53. data/public/css/bootstrap.min.css +11 -0
  54. data/public/css/main.css +130 -0
  55. data/public/css/tomorrow-night.css +75 -0
  56. data/public/fonts/glyphicons-halflings-regular.eot +0 -0
  57. data/public/fonts/glyphicons-halflings-regular.svg +273 -214
  58. data/public/fonts/glyphicons-halflings-regular.ttf +0 -0
  59. data/public/fonts/glyphicons-halflings-regular.woff +0 -0
  60. data/public/fonts/glyphicons-halflings-regular.woff2 +0 -0
  61. data/public/images/blob_spinner.gif +0 -0
  62. data/public/images/gear_spinner.gif +0 -0
  63. data/public/js/bootstrap.min.js +7 -0
  64. data/public/js/highlight.pack.js +2 -0
  65. data/public/js/highlight.worker.js +13 -0
  66. data/public/js/jquery-2.1.4.min.js +4 -0
  67. data/public/js/main.js +239 -0
  68. data/public/robots.txt +2 -0
  69. data/signatures.json +541 -0
  70. data/views/assessments/_assessments.erb +57 -0
  71. data/views/assessments/_comparable_assessments.erb +38 -0
  72. data/views/assessments/_comparisons.erb +111 -0
  73. data/views/assessments/compare.erb +22 -0
  74. data/views/assessments/findings.erb +55 -0
  75. data/views/assessments/repositories.erb +35 -0
  76. data/views/assessments/show.erb +1 -0
  77. data/views/assessments/users.erb +46 -0
  78. data/views/blobs/show.erb +37 -0
  79. data/views/comparisons/show.erb +125 -0
  80. data/views/errors/internal_server_error.erb +9 -0
  81. data/views/errors/not_found.erb +5 -0
  82. data/views/index.erb +43 -28
  83. data/views/layout.erb +38 -12
  84. data/views/repositories/show.erb +49 -0
  85. data/views/users/show.erb +54 -0
  86. metadata +217 -106
  87. data/bin/gitrob +0 -260
  88. data/lib/gitrob/github/blob.rb +0 -41
  89. data/lib/gitrob/github/http_client.rb +0 -127
  90. data/lib/gitrob/github/organization.rb +0 -99
  91. data/lib/gitrob/github/repository.rb +0 -72
  92. data/lib/gitrob/github/user.rb +0 -84
  93. data/lib/gitrob/observers/sensitive_files.rb +0 -83
  94. data/lib/gitrob/progressbar.rb +0 -52
  95. data/lib/gitrob/util.rb +0 -11
  96. data/lib/gitrob/webapp.rb +0 -76
  97. data/models/blob.rb +0 -35
  98. data/models/finding.rb +0 -14
  99. data/models/organization.rb +0 -32
  100. data/models/repo.rb +0 -22
  101. data/models/user.rb +0 -28
  102. data/patterns.json +0 -394
  103. data/public/javascripts/bootstrap.min.js +0 -7
  104. data/public/javascripts/gitrob.js +0 -75
  105. data/public/javascripts/jquery-2.1.1.min.js +0 -4
  106. data/public/javascripts/lang-apollo.js +0 -2
  107. data/public/javascripts/lang-basic.js +0 -3
  108. data/public/javascripts/lang-clj.js +0 -18
  109. data/public/javascripts/lang-css.js +0 -2
  110. data/public/javascripts/lang-dart.js +0 -3
  111. data/public/javascripts/lang-erlang.js +0 -2
  112. data/public/javascripts/lang-go.js +0 -1
  113. data/public/javascripts/lang-hs.js +0 -2
  114. data/public/javascripts/lang-lisp.js +0 -3
  115. data/public/javascripts/lang-llvm.js +0 -1
  116. data/public/javascripts/lang-lua.js +0 -2
  117. data/public/javascripts/lang-matlab.js +0 -6
  118. data/public/javascripts/lang-ml.js +0 -2
  119. data/public/javascripts/lang-mumps.js +0 -2
  120. data/public/javascripts/lang-n.js +0 -4
  121. data/public/javascripts/lang-pascal.js +0 -3
  122. data/public/javascripts/lang-proto.js +0 -1
  123. data/public/javascripts/lang-r.js +0 -2
  124. data/public/javascripts/lang-rd.js +0 -1
  125. data/public/javascripts/lang-scala.js +0 -2
  126. data/public/javascripts/lang-sql.js +0 -2
  127. data/public/javascripts/lang-tcl.js +0 -3
  128. data/public/javascripts/lang-tex.js +0 -1
  129. data/public/javascripts/lang-vb.js +0 -2
  130. data/public/javascripts/lang-vhdl.js +0 -3
  131. data/public/javascripts/lang-wiki.js +0 -2
  132. data/public/javascripts/lang-xq.js +0 -3
  133. data/public/javascripts/lang-yaml.js +0 -2
  134. data/public/javascripts/prettify.js +0 -30
  135. data/public/javascripts/run_prettify.js +0 -34
  136. data/public/stylesheets/bootstrap.min.css +0 -7
  137. data/public/stylesheets/bootstrap.min.css.vanilla +0 -5
  138. data/public/stylesheets/gitrob.css +0 -88
  139. data/public/stylesheets/prettify.css +0 -51
  140. data/spec/lib/gitrob/observers/sensitive_files_spec.rb +0 -691
  141. data/spec/spec_helper.rb +0 -127
  142. data/views/blob.erb +0 -22
  143. data/views/organization.erb +0 -126
  144. data/views/repository.erb +0 -51
  145. data/views/user.erb +0 -51
@@ -0,0 +1,11 @@
1
+ Sequel.migration do
2
+ change do
3
+ create_table(:github_access_tokens) do
4
+ primary_key :id
5
+ foreign_key :assessment_id, :assessments, :on_delete => :cascade, :index => true
6
+ String :token, :size => 40, :fixed => true
7
+ DateTime :updated_at
8
+ DateTime :created_at
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,24 @@
1
+ Sequel.migration do
2
+ change do
3
+ create_table(:owners) do
4
+ primary_key :id
5
+ Integer :github_id, :index => true
6
+ foreign_key :assessment_id, :assessments, :on_delete => :cascade, :index => true
7
+ String :login
8
+ String :type, :size => 12, :index => true
9
+ String :url
10
+ String :html_url
11
+ String :avatar_url
12
+ String :name
13
+ String :blog
14
+ String :location
15
+ String :email
16
+ String :bio
17
+ Integer :repositories_count, :index => true, :default => 0
18
+ Integer :blobs_count, :index => true, :default => 0
19
+ Integer :findings_count, :index => true, :default => 0
20
+ DateTime :updated_at
21
+ DateTime :created_at
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ Sequel.migration do
2
+ change do
3
+ create_table(:repositories) do
4
+ primary_key :id
5
+ Integer :github_id, :index => true
6
+ foreign_key :assessment_id, :assessments, :on_delete => :cascade, :index => true
7
+ foreign_key :owner_id, :owners, :on_delete => :cascade, :index => true
8
+ String :name
9
+ String :full_name
10
+ String :description
11
+ Boolean :private
12
+ String :url
13
+ String :html_url
14
+ String :homepage
15
+ Integer :size
16
+ String :default_branch
17
+ Integer :blobs_count, :default => 0
18
+ Integer :findings_count, :index => true, :default => 0
19
+ DateTime :updated_at
20
+ DateTime :created_at
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,16 @@
1
+ Sequel.migration do
2
+ change do
3
+ create_table(:blobs) do
4
+ primary_key :id
5
+ foreign_key :assessment_id, :assessments, :on_delete => :cascade, :index => true
6
+ foreign_key :owner_id, :owners, :on_delete => :cascade, :index => true
7
+ foreign_key :repository_id, :repositories, :on_delete => :cascade, :index => true
8
+ String :path
9
+ Integer :size, :index => true
10
+ String :sha, :size => 40, :fixed => true, :index => true
11
+ Integer :flags_count, :index => true, :default => 0
12
+ DateTime :updated_at
13
+ DateTime :created_at
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ Sequel.migration do
2
+ change do
3
+ create_table(:flags) do
4
+ primary_key :id
5
+ foreign_key :assessment_id, :assessments, :on_delete => :cascade, :index => true
6
+ foreign_key :blob_id, :blobs, :on_delete => :cascade, :index => true
7
+ String :caption
8
+ String :description
9
+ DateTime :updated_at
10
+ DateTime :created_at
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ Sequel.migration do
2
+ change do
3
+ create_table(:comparisons) do
4
+ primary_key :id
5
+ foreign_key :primary_assessment_id, :assessments, :on_delete => :cascade, :index => true
6
+ foreign_key :secondary_assessment_id, :assessments, :on_delete => :cascade, :index => true
7
+ Integer :blobs_count, :default => 0
8
+ Integer :repositories_count, :default => 0
9
+ Integer :owners_count, :default => 0
10
+ Integer :findings_count, :default => 0
11
+ Boolean :finished, :default => false, :index => true
12
+ Boolean :deleted, :default => false, :index => true
13
+ DateTime :updated_at
14
+ DateTime :created_at
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,8 @@
1
+ Sequel.migration do
2
+ change do
3
+ create_table(:blobs_comparisons) do
4
+ foreign_key :comparison_id, :comparisons, :on_delete => :cascade, :index => true
5
+ foreign_key :blob_id, :blobs, :on_delete => :cascade, :index => true
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ Sequel.migration do
2
+ change do
3
+ create_table(:comparisons_repositories) do
4
+ foreign_key :comparison_id, :comparisons, :on_delete => :cascade, :index => true
5
+ foreign_key :repository_id, :repositories, :on_delete => :cascade, :index => true
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ Sequel.migration do
2
+ change do
3
+ create_table(:comparisons_owners) do
4
+ foreign_key :comparison_id, :comparisons, :on_delete => :cascade, :index => true
5
+ foreign_key :owner_id, :owners, :on_delete => :cascade, :index => true
6
+ end
7
+ end
8
+ end
data/exe/gitrob ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # :nocov:
3
+ require "gitrob"
4
+
5
+ Gitrob::CLI.start(ARGV)
6
+ # :nocov:
data/gitrob.gemspec CHANGED
@@ -1,36 +1,43 @@
1
1
  # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
2
+ lib = File.expand_path("../lib", __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'gitrob/version'
4
+ require "gitrob/version"
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "gitrob"
8
8
  spec.version = Gitrob::VERSION
9
9
  spec.authors = ["Michael Henriksen"]
10
10
  spec.email = ["michenriksen@neomailbox.ch"]
11
- spec.summary = %q{Reconnaissance tool for GitHub organizations.}
12
- spec.description = %q{Reconnaissance tool for GitHub organizations.}
11
+
12
+ spec.summary = %q{Reconnaissance tool for GitHub organizations}
13
13
  spec.homepage = "https://github.com/michenriksen/gitrob"
14
14
  spec.license = "MIT"
15
15
 
16
- spec.files = `git ls-files -z`.split("\x0")
17
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_dependency "httparty", "~> 0.13"
22
- spec.add_dependency "methadone", "~> 1.7"
23
- spec.add_dependency "highline", "~> 1.6"
24
- spec.add_dependency "paint", "~> 0.8"
25
- spec.add_dependency "ruby-progressbar", "~> 1.6"
26
- spec.add_dependency "thread", "~> 0.1"
21
+ spec.add_dependency "thor", "~> 0.19"
22
+ spec.add_dependency "colorize", "~> 0.7"
23
+ spec.add_dependency "highline", "~> 1.7"
24
+ spec.add_dependency "thread", "~> 0.2"
25
+ spec.add_dependency "ruby-progressbar", "~> 1.7"
27
26
  spec.add_dependency "sinatra", "~> 1.4"
28
27
  spec.add_dependency "thin", "~> 1.6"
29
- spec.add_dependency "datamapper", "~> 1.2"
30
- spec.add_dependency "dm-postgres-adapter"
28
+ spec.add_dependency "pg", "~> 0.18"
29
+ spec.add_dependency "sequel", "~> 4.27"
30
+ spec.add_dependency "github_api", "~> 0.12"
31
+ spec.add_dependency "sucker_punch", "~> 2.0", ">= 2.0.1"
31
32
 
32
- spec.add_development_dependency "bundler", "~> 1.7"
33
+ spec.add_development_dependency "bundler", "~> 1.10"
33
34
  spec.add_development_dependency "rake", "~> 10.0"
34
- spec.add_development_dependency "rspec", "~> 3.1"
35
- spec.add_development_dependency "webmock", "~> 1.20"
35
+ spec.add_development_dependency "rspec"
36
+ spec.add_development_dependency "simplecov"
37
+ spec.add_development_dependency "timecop"
38
+ spec.add_development_dependency "rubocop"
39
+ spec.add_development_dependency "factory_girl"
40
+ spec.add_development_dependency "faker"
41
+ spec.add_development_dependency "awesome_print"
42
+ spec.add_development_dependency "webmock"
36
43
  end
@@ -0,0 +1,103 @@
1
+ module Gitrob
2
+ class BlobObserver
3
+ SIGNATURES_FILE_PATH = File.expand_path(
4
+ "../../../signatures.json", __FILE__)
5
+
6
+ REQUIRED_SIGNATURE_KEYS = %w(part type pattern caption description)
7
+ ALLOWED_TYPES = %w(regex match)
8
+ ALLOWED_PARTS = %w(path filename extension)
9
+
10
+ class Signature < OpenStruct; end
11
+ class CorruptSignaturesError < StandardError; end
12
+
13
+ def self.observe(blob)
14
+ signatures.each do |signature|
15
+ if signature.type == "match"
16
+ observe_with_match_signature(blob, signature)
17
+ else
18
+ observe_with_regex_signature(blob, signature)
19
+ end
20
+ end
21
+ blob.flags_count = blob.flags.count
22
+ blob.save
23
+ blob.flags_count
24
+ end
25
+
26
+ def self.signatures
27
+ load_signatures! unless @signatures
28
+ @signatures
29
+ end
30
+
31
+ def self.load_signatures!
32
+ @signatures = []
33
+ JSON.load(File.read(SIGNATURES_FILE_PATH)).each do |signature|
34
+ @signatures << Signature.new(signature)
35
+ end
36
+ validate_signatures!
37
+ rescue CorruptSignaturesError => e
38
+ raise e
39
+ rescue
40
+ raise CorruptSignaturesError, "Could not parse signature file"
41
+ end
42
+
43
+ def self.validate_signatures!
44
+ if !signatures.is_a?(Array) || signatures.empty?
45
+ fail CorruptSignaturesError,
46
+ "Signature file contains no signatures"
47
+ end
48
+ signatures.each do |signature|
49
+ validate_signature!(signature)
50
+ end
51
+ end
52
+
53
+ def self.validate_signature!(signature)
54
+ validate_signature_keys!(signature)
55
+ validate_signature_type!(signature)
56
+ validate_signature_part!(signature)
57
+ end
58
+
59
+ def self.validate_signature_keys!(signature)
60
+ REQUIRED_SIGNATURE_KEYS.each do |key|
61
+ unless signature.respond_to?(key)
62
+ fail CorruptSignaturesError,
63
+ "Missing required signature key: #{key}"
64
+ end
65
+ end
66
+ end
67
+
68
+ def self.validate_signature_type!(signature)
69
+ unless ALLOWED_TYPES.include?(signature.type)
70
+ fail CorruptSignaturesError,
71
+ "Invalid signature type: #{signature.type}"
72
+ end
73
+ end
74
+
75
+ def self.validate_signature_part!(signature)
76
+ unless ALLOWED_PARTS.include?(signature.part)
77
+ fail CorruptSignaturesError,
78
+ "Invalid signature part: #{signature.part}"
79
+ end
80
+ end
81
+
82
+ def self.observe_with_match_signature(blob, signature)
83
+ haystack = blob.send(signature.part.to_sym)
84
+ return unless haystack == signature.pattern
85
+ blob.add_flag(
86
+ :caption => signature.caption,
87
+ :description => signature.description,
88
+ :assessment => blob.assessment
89
+ )
90
+ end
91
+
92
+ def self.observe_with_regex_signature(blob, signature)
93
+ haystack = blob.send(signature.part.to_sym)
94
+ regex = Regexp.new(signature.pattern, Regexp::IGNORECASE)
95
+ return if regex.match(haystack).nil?
96
+ blob.add_flag(
97
+ :caption => signature.caption,
98
+ :description => signature.description,
99
+ :assessment => blob.assessment
100
+ )
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,58 @@
1
+ module Gitrob
2
+ class CLI
3
+ class Command
4
+ attr_reader :options
5
+
6
+ def self.start(*args)
7
+ new(*args)
8
+ end
9
+
10
+ def initialize(*args) # rubocop:disable Lint/UnusedMethodArgument
11
+ end
12
+
13
+ def info(*args)
14
+ Gitrob::CLI.info(*args)
15
+ end
16
+
17
+ def task(message, fatal_error=false, &block)
18
+ Gitrob::CLI.task(message, fatal_error, &block)
19
+ end
20
+
21
+ def warn(*args)
22
+ Gitrob::CLI.warn(*args)
23
+ end
24
+
25
+ def error(*args)
26
+ Gitrob::CLI.error(*args)
27
+ end
28
+
29
+ def fatal(*args)
30
+ Gitrob::CLI.fatal(*args)
31
+ end
32
+
33
+ def debug(*args)
34
+ Gitrob::CLI.debug(*args)
35
+ end
36
+
37
+ def debugging_enabled?
38
+ Gitrob::CLI.debugging_enabled?
39
+ end
40
+
41
+ def output(*args)
42
+ Gitrob::CLI.output(*args)
43
+ end
44
+
45
+ def thread_pool
46
+ pool = Thread::Pool.new(options[:threads] || 5)
47
+ yield pool
48
+ pool.shutdown
49
+ end
50
+
51
+ def progress_bar(message, options)
52
+ progress_bar = Gitrob::CLI::ProgressBar.new(message, options)
53
+ yield progress_bar
54
+ progress_bar.finish
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,61 @@
1
+ module Gitrob
2
+ class CLI
3
+ module Commands
4
+ class AcceptTermsOfUse < Gitrob::CLI::Command
5
+ AGREEMENT_FILE_PATH = File.expand_path(
6
+ "../../../../../agreement.txt", __FILE__)
7
+
8
+ LICENSE_FILE_PATH = File.expand_path(
9
+ "../../../../../LICENSE.txt", __FILE__)
10
+
11
+ AGREEMENT = "Gitrob is designed for security professionals. " \
12
+ "If you use any information\nfound through this " \
13
+ "tool for malicious purposes that are not " \
14
+ "authorized by\nthe target, you are " \
15
+ "violating the terms of use and license of " \
16
+ "this\ntool. By typing y/yes, you agree to the " \
17
+ "terms of use and that you will use\nthis tool " \
18
+ "for lawful purposes only."
19
+
20
+ VALID_ANSWERS = %w(y yes n no)
21
+ POSITIVE_ANSWERS = %w(y yes)
22
+
23
+ def initialize(options)
24
+ @options = options
25
+ return if terms_of_use_accepted?
26
+ present_terms_of_use
27
+ handle_user_input
28
+ end
29
+
30
+ private
31
+
32
+ def terms_of_use_accepted?
33
+ File.exist?(AGREEMENT_FILE_PATH)
34
+ end
35
+
36
+ def present_terms_of_use
37
+ output "\n#{license}\n\n"
38
+ output AGREEMENT.light_red
39
+ end
40
+
41
+ def handle_user_input
42
+ if agree("\n\nDo you agree to the terms of use? (y/n): ".light_green)
43
+ accept_terms_of_use
44
+ else
45
+ fatal("Exiting Gitrob.")
46
+ end
47
+ end
48
+
49
+ def accept_terms_of_use
50
+ File.open(AGREEMENT_FILE_PATH, "w") do |file|
51
+ file.write("user accepted")
52
+ end
53
+ end
54
+
55
+ def license
56
+ File.read(LICENSE_FILE_PATH)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,75 @@
1
+ module Gitrob
2
+ class CLI
3
+ module Commands
4
+ class Analyze < Gitrob::CLI::Command
5
+ module Analysis
6
+ def analyze_repositories
7
+ repo_progress_bar do |progress|
8
+ github_data_manager.owners.each do |owner|
9
+ @db_owner = @db_assessment.save_owner(owner)
10
+ thread_pool do |pool|
11
+ repositories_for_owner(owner).each do |repo|
12
+ pool.process do
13
+ db_repo = @db_assessment.save_repository(repo, @db_owner)
14
+ blobs = blobs_for_repository(repo)
15
+ analyze_blobs(blobs, db_repo, @db_owner, progress)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ def analyze_blobs(blobs, db_repo, db_owner, progress)
24
+ findings = 0
25
+ blobs.each do |blob|
26
+ db_blob = @db_assessment.save_blob(blob, db_repo, db_owner)
27
+ Gitrob::BlobObserver.observe(db_blob)
28
+ if db_blob.flags.count > 0
29
+ findings += 1
30
+ @db_assessment.findings_count += 1
31
+ db_owner.findings_count += 1
32
+ db_repo.findings_count += 1
33
+ end
34
+ end
35
+ db_owner.save
36
+ db_repo.save
37
+ progress.increment
38
+ report_findings(findings, db_repo, progress)
39
+ rescue => e
40
+ progress.error("#{e.class}: #{e.message}")
41
+ end
42
+
43
+ def report_findings(finding_count, repo, progress)
44
+ return if finding_count.zero?
45
+ files = finding_count == 1 ? "1 file" : "#{finding_count} files"
46
+ progress.info(
47
+ "Flagged #{files.to_s.light_yellow} " \
48
+ "in #{repo.full_name.bold}")
49
+ end
50
+ end
51
+
52
+ def repo_progress_bar
53
+ progress_bar(
54
+ "Analyzing repositories...",
55
+ :total => repository_count) do |progress|
56
+ yield progress
57
+ end
58
+ sleep 0.1
59
+ end
60
+
61
+ def repositories_for_owner(owner)
62
+ github_data_manager.repositories_for_owner(owner)
63
+ end
64
+
65
+ def blobs_for_repository(repo)
66
+ github_data_manager.blobs_for_repository(repo)
67
+ end
68
+
69
+ def repository_count
70
+ @github_data_manager.repositories.count
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,101 @@
1
+ module Gitrob
2
+ class CLI
3
+ module Commands
4
+ class Analyze < Gitrob::CLI::Command
5
+ module Gathering
6
+ def gather_owners
7
+ task("Gathering targets...") do
8
+ thread_pool do |pool|
9
+ github_data_manager.gather_owners(pool)
10
+ end
11
+ end
12
+ fatal("No users or organizations " \
13
+ "found; exiting") if owner_count.zero?
14
+ end
15
+
16
+ def gather_repositories
17
+ make_repo_gathering_progress_bar do |progress|
18
+ thread_pool do |pool|
19
+ github_data_manager
20
+ .gather_repositories(pool) do |owner, repositories|
21
+ repository_gathering_update(owner, repositories, progress)
22
+ end
23
+ end
24
+ end
25
+ fatal("No repositories found; exiting") if repo_count.zero?
26
+ info "Gathered #{repo_count} repositories"
27
+ end
28
+
29
+ def github_data_manager
30
+ unless @github_data_manager
31
+ @github_data_manager = Gitrob::Github::DataManager.new(
32
+ @targets, github_client_manager
33
+ )
34
+ end
35
+ @github_data_manager
36
+ end
37
+
38
+ def github_client_manager
39
+ unless @github_client_manager
40
+ @github_client_manager = Gitrob::Github::ClientManager.new(
41
+ client_manager_configuration
42
+ )
43
+ end
44
+ @github_client_manager
45
+ end
46
+
47
+ def client_manager_configuration
48
+ {
49
+ :endpoint => @options[:endpoint],
50
+ :site => @options[:site],
51
+ :ssl => {
52
+ :verify => @options[:verify_ssl]
53
+ },
54
+ :access_tokens => github_access_tokens
55
+ }
56
+ end
57
+
58
+ def github_access_tokens
59
+ if @options[:access_tokens]
60
+ @options[:access_tokens].gsub("\n", ",").split(",").map(&:strip)
61
+ else
62
+ Gitrob::CLI.configuration["github_access_tokens"]
63
+ end
64
+ end
65
+
66
+ def make_repo_gathering_progress_bar
67
+ total = github_data_manager.owners.count
68
+ progress_bar(
69
+ "Gathering repositories from #{total} targets...",
70
+ :total => total
71
+ ) do |progress|
72
+ yield progress
73
+ end
74
+ sleep 0.1
75
+ end
76
+
77
+ def repo_count
78
+ github_data_manager.repositories.count
79
+ end
80
+
81
+ def owner_count
82
+ github_data_manager.owners.count
83
+ end
84
+
85
+ def repository_gathering_update(owner, repositories, progress_bar)
86
+ count = repositories.count
87
+ progress_bar.increment
88
+ return if count.zero?
89
+ gathered = Gitrob::Utils.pluralize(
90
+ count,
91
+ "repository",
92
+ "repositories"
93
+ )
94
+ progress_bar.info("Gathered #{gathered} " \
95
+ "from #{owner['login'].bold}")
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,63 @@
1
+ require "gitrob/cli/commands/analyze/gathering"
2
+ require "gitrob/cli/commands/analyze/analysis"
3
+
4
+ module Gitrob
5
+ class CLI
6
+ module Commands
7
+ class Analyze < Gitrob::CLI::Command
8
+ include Gathering
9
+ include Analysis
10
+
11
+ def initialize(targets, options)
12
+ Thread.abort_on_exception = true
13
+ @options = options
14
+ @targets = targets.split(",").map(&:strip).uniq
15
+ load_signatures!
16
+ create_database_assessment
17
+ gather_owners
18
+ gather_repositories
19
+ analyze_repositories
20
+ @db_assessment.finished = true
21
+ @db_assessment.save
22
+ start_web_server
23
+ end
24
+
25
+ private
26
+
27
+ def create_database_assessment
28
+ @db_assessment = Gitrob::Models::Assessment.create(
29
+ :endpoint => @options[:endpoint],
30
+ :site => @options[:site],
31
+ :verify_ssl => @options[:verify_ssl],
32
+ :finished => false
33
+ )
34
+ github_access_tokens.each do |access_token|
35
+ @db_assessment.save_github_access_token(access_token)
36
+ end
37
+ end
38
+
39
+ def load_signatures!
40
+ task("Loading signatures...", true) do
41
+ Gitrob::BlobObserver.load_signatures!
42
+ end
43
+ end
44
+
45
+ def start_web_server
46
+ return unless options[:server]
47
+ info "Starting web application on port #{options[:port]}..."
48
+ info "Browse to http://#{options[:bind_address]}:" \
49
+ "#{options[:port]}/ to see results!"
50
+
51
+ if debugging_enabled?
52
+ Sequel::Model.db.logger = QueryLogger.new(STDOUT)
53
+ end
54
+
55
+ Gitrob::WebApp.run!(
56
+ :port => options[:port].to_i,
57
+ :bind => options[:bind_address]
58
+ )
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end