redmine_audit 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/init.rb CHANGED
@@ -1,8 +1,9 @@
1
1
  Redmine::Plugin.register :redmine_audit do
2
2
  name 'Redmine Audit plugin'
3
3
  author 'Sho Hashimoto'
4
- description 'Redmine plugin for checking vulnerabilities'
4
+ description 'Redmine plugin for checking Redmine\'s own vulnerabilities'
5
5
  version RedmineAudit::VERSION
6
+ requires_redmine version_or_higher: '3.3.0'
6
7
  url 'https://github.com/sho-h/redmine_audit/'
7
8
  author_url 'https://github.com/sho-h/'
8
9
  end
@@ -1,13 +1,23 @@
1
1
  module RedmineAudit
2
- # Redmine advisory
2
+ # Redmine/Redmine plugin advisory
3
3
  #
4
4
  # ex) Advisory.new('High', 'http://someurl.example.com',
5
- # [Gem::Requirement], [Gem::Requirement])
6
- class Advisory < Struct.new(:severity,
7
- :details,
8
- :external_references,
9
- :unaffected_versions,
10
- :fixed_versions)
5
+ # [Gem::Requirement], [Gem::Requirement], 'CVE-XXXX-XXXX', nil, 7.5)
6
+ Advisory = Struct.new(:severity,
7
+ :details,
8
+ :external_references,
9
+ :unaffected_versions,
10
+ :fixed_versions,
11
+ :id, :cvss_v2, :cvss_v3) do
12
+ def initialize(severity, details, external_references,
13
+ unaffected_versions, fixed_versions, id, cvss_v2, cvss_v3)
14
+ super
15
+ self.severity = cvss_to_severity if self.severity.nil?
16
+ [:external_references, :unaffected_versions, :fixed_versions].each do |attr|
17
+ self[attr] = [] if self[attr].nil?
18
+ end
19
+ end
20
+
11
21
  # Checks whether the version is not affected by the advisory.
12
22
  #
13
23
  # @param [Gem::Version] version
@@ -44,5 +54,37 @@ module RedmineAudit
44
54
  def vulnerable?(version)
45
55
  !fixed?(version) && !unaffected?(version)
46
56
  end
57
+
58
+ private
59
+
60
+ def cvss_to_severity
61
+ if cvss_v2
62
+ case cvss_v2
63
+ when 0.0..3.9
64
+ 'Low'
65
+ when 4.0..6.9
66
+ 'Medium'
67
+ when 7.0..10.0
68
+ 'High'
69
+ else
70
+ raise ArgumentError, "invalid CVSS v2 score: #{cvss_v2}"
71
+ end
72
+ elsif cvss_v3
73
+ case cvss_v3
74
+ when 0.0
75
+ 'None'
76
+ when 0.1..3.9
77
+ 'Low'
78
+ when 4.0..6.9
79
+ 'Medium'
80
+ when 7.0..8.9
81
+ 'High'
82
+ when 9.0..10
83
+ 'Critical'
84
+ else
85
+ raise ArgumentError, "invalid CVSS v3 score: #{cvss_v3}"
86
+ end
87
+ end
88
+ end
47
89
  end
48
90
  end
@@ -8,8 +8,6 @@ module RedmineAudit
8
8
  URL = 'http://www.redmine.org/projects/redmine/wiki/Security_Advisories'
9
9
  TABLE_XPATH = '//*[@id="content"]/div[2]/table'
10
10
 
11
- attr_reader :vulnerabilities
12
-
13
11
  # Get unfixed advisories against specified Redmine version.
14
12
  #
15
13
  # @param [String] version
@@ -24,7 +22,7 @@ module RedmineAudit
24
22
  doc = Nokogiri::HTML(html)
25
23
  doc.xpath(TABLE_XPATH).xpath('tr')[1..-1].each do |tr|
26
24
  if res = parse_tds(tr.xpath('td'))
27
- @known_advisories << Advisory.new(*res)
25
+ @known_advisories << Advisory.new(*res, nil, nil, nil)
28
26
  end
29
27
  end
30
28
  end
@@ -0,0 +1,62 @@
1
+ require 'open-uri'
2
+ require 'yaml'
3
+ # TODO: use Redmine's Redmine::Plugin
4
+ begin
5
+ require 'redmine/plugin'
6
+ rescue LoadError
7
+ end
8
+ require 'redmine_audit/advisory'
9
+
10
+ module RedmineAudit
11
+ # Redmine plugin advisory database
12
+ class PluginDatabase
13
+ URL = 'https://raw.githubusercontent.com/sho-h/redmine_audit/master/data/plugin_advisories.yml'
14
+
15
+ # Get unfixed plugin advisories against specified Redmine version.
16
+ #
17
+ # @return [[Redmine::Advisory]]
18
+ # The array of plugin's Redmine::Advisory unfixed.
19
+ def advisories
20
+ if @known_advisories.nil?
21
+ @known_advisories = {}
22
+ YAML.load(fetch_advisory_data).each do |plugin_id, advisories|
23
+ @known_advisories[plugin_id] ||= []
24
+ advisories.each do |cve_id, attrs|
25
+ unaffected_vers = (attrs['unaffected_versions'] || []).map { |ver|
26
+ Gem::Requirement.new(ver)
27
+ }
28
+ patched_vers = (attrs['patched_versions'] || []).map { |ver|
29
+ Gem::Requirement.new(ver)
30
+ }
31
+ args = [
32
+ nil, attrs['title'], [attrs['url']],
33
+ unaffected_vers, patched_vers,
34
+ cve_id, attrs['cvss_v2'], attrs['cvss_v3'],
35
+ ]
36
+ @known_advisories[plugin_id] << Advisory.new(*args)
37
+ end
38
+ end
39
+ end
40
+
41
+ unfixed_advisories = {}
42
+ Redmine::Plugin.all.each do |plugin|
43
+ advisories = @known_advisories[plugin.id]
44
+ next if advisories.nil? || advisories.empty?
45
+
46
+ advisories.each do |advisory|
47
+ if advisory.vulnerable?(Gem::Version.new(plugin.version))
48
+ unfixed_advisories[plugin] ||= []
49
+ unfixed_advisories[plugin].push(advisory)
50
+ end
51
+ end
52
+ end
53
+ return unfixed_advisories
54
+ end
55
+
56
+ private
57
+
58
+ def fetch_advisory_data(url = URL)
59
+ open(url).read
60
+ end
61
+ end
62
+ end
@@ -1,3 +1,3 @@
1
1
  module RedmineAudit
2
- VERSION = '0.1.1'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -1,32 +1,70 @@
1
1
  require 'redmine/version'
2
2
  require 'redmine_audit/advisory'
3
3
  require 'redmine_audit/database'
4
+ require 'redmine_audit/plugin_database'
5
+ require 'bundler/audit/scanner'
6
+ require 'ruby_audit/database'
7
+ require 'ruby_audit/scanner'
4
8
 
5
9
  desc <<-END_DESC
6
10
  Check redmine vulnerabilities.
7
11
 
8
12
  Available options:
9
- * users => comma separated list of user/group ids who should be notified
13
+ * users => comma separated list of user/group ids who should be notified
14
+ * dont_check_ruby_and_gems => don't bundler-audit and ruby_audit if '1' specified
10
15
 
11
16
  Example:
12
- rake redmine:bundle_audit users="1,23, 56" RAILS_ENV="production"
17
+ rake redmine:audit users="1,23, 56" RAILS_ENV="production"
13
18
  END_DESC
14
19
 
15
20
  namespace :redmine do
16
- # Avoid to define same task twice.
17
- # TODO: stop load twice this .rake file.
18
- if !Rake::Task.task_defined?(:audit)
19
- task audit: :environment do
20
- # TODO: More better if requires mailer automatically.
21
- require_dependency 'mailer'
22
- require_relative '../../app/models/mailer.rb'
23
-
24
- redmine_ver = Redmine::VERSION
25
- advisories = RedmineAudit::Database.new.advisories(redmine_ver.to_s)
26
- if advisories.length > 0
27
- users = (ENV['users'] || '').split(',').each(&:strip!)
21
+ task audit: :environment do
22
+ user_ids = (ENV['users'] || '').split(',').each(&:strip!)
23
+ if user_ids.empty?
24
+ raise 'need to specify environment variable: users'
25
+ end
26
+
27
+ users = User.active.where(admin: true, id: user_ids).to_a
28
+ if users.empty?
29
+ raise ActiveRecord::RecordNotFound.new("Couldn't find user specified: #{user_ids.inspect}")
30
+ end
31
+
32
+ # TODO: More better if requires mailer automatically.
33
+ require_dependency 'mailer'
34
+ require_relative '../../app/models/mailer.rb'
35
+ Rake::Task['redmine:audit:redmine'].invoke(users)
36
+ unless ENV['dont_check_ruby_and_gems'] == '1'
37
+ Rake::Task['redmine:audit:ruby_and_gems'].invoke(users)
38
+ end
39
+ end
40
+
41
+ namespace :audit do
42
+ task :redmine, [:users] do |t, args|
43
+ redmine_ver = Redmine::VERSION.to_s
44
+ advisories = RedmineAudit::Database.new.advisories(redmine_ver)
45
+ plugin_advisories = RedmineAudit::PluginDatabase.new.advisories
46
+ if advisories.length > 0 || plugin_advisories.length > 0
47
+ Mailer.with_synched_deliveries do
48
+ Mailer.unfixed_redmine_advisories_found(redmine_ver, advisories, plugin_advisories, args.users).deliver
49
+ end
50
+ end
51
+ end
52
+
53
+ # Update ruby-advisory-db.
54
+ task :update_ruby_advisory_db do
55
+ begin
56
+ Bundler::Audit::Database.update!(quiet: true)
57
+ rescue => e
58
+ warn "failed to update ruby-advisory-db: #{e.message}"
59
+ end
60
+ end
61
+
62
+ task :ruby_and_gems, [:users] => :update_ruby_advisory_db do |t, args|
63
+ ruby_result = RubyAudit::Scanner.new.scan
64
+ gems_result = Bundler::Audit::Scanner.new.scan
65
+ if ruby_result.count > 0 || gems_result.count > 0
28
66
  Mailer.with_synched_deliveries do
29
- Mailer.unfixed_advisories_found(redmine_ver, advisories, users).deliver
67
+ Mailer.unfixed_ruby_and_gems_advisories_found(ruby_result, gems_result, args.users).deliver
30
68
  end
31
69
  end
32
70
  end
@@ -22,9 +22,12 @@ Gem::Specification.new do |spec|
22
22
  spec.require_paths = ['lib']
23
23
 
24
24
  spec.add_runtime_dependency 'nokogiri'
25
+ spec.add_runtime_dependency 'bundler-audit'
26
+ spec.add_runtime_dependency 'ruby_audit'
25
27
 
26
28
  spec.add_development_dependency 'bundler', '~> 1.14'
27
29
  spec.add_development_dependency 'rake', '~> 10.0'
28
30
  spec.add_development_dependency 'test-unit'
29
31
  spec.add_development_dependency 'test-unit-rr'
32
+ spec.add_development_dependency 'travis'
30
33
  end
@@ -0,0 +1,42 @@
1
+ require 'open-uri'
2
+ require 'nokogiri'
3
+ require 'yaml'
4
+
5
+ URL_BASE = 'http://www.redmine.org/'
6
+ LIST_PATH = File.join(__dir__, '../data/plugins.yml')
7
+
8
+ plugins = {}
9
+ page = 1
10
+ loop do
11
+ puts "chacking page:#{page}..."
12
+ doc = Nokogiri::HTML(open(URL_BASE + "plugins?page=#{page}").read)
13
+ doc.xpath('//a[@class="plugin"]').each do |a|
14
+ path = a.attributes["href"].value
15
+ name = path.split('/').last
16
+ plugins[name] = {}
17
+
18
+ sleep 60
19
+ puts "chacking page:#{page}:#{name}..."
20
+
21
+ detail_url = File.join(URL_BASE, path)
22
+ detail_doc = Nokogiri::HTML(open(detail_url).read)
23
+ {website: 2, repository: 3}.each do |key, index|
24
+ xpath = "//*[@id=\"content\"]/div[2]/table/tr[#{index}]/td/a"
25
+ detail_doc.xpath(xpath).each do |da|
26
+ plugins[name][key] = da.attributes["href"].value
27
+ end
28
+ end
29
+ end
30
+ if doc.xpath('//a[@class="next"]').count > 0
31
+ page += 1
32
+ else
33
+ break
34
+ end
35
+
36
+ File.open(LIST_PATH, 'w') do |f|
37
+ f.write(plugins.to_yaml)
38
+ end
39
+
40
+ sleep 60
41
+ end
42
+
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redmine_audit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sho Hashimoto
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-06-17 00:00:00.000000000 Z
11
+ date: 2018-06-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -24,6 +24,34 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler-audit
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: ruby_audit
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: bundler
29
57
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +108,20 @@ dependencies:
80
108
  - - ">="
81
109
  - !ruby/object:Gem::Version
82
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: travis
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
83
125
  description: Redmine plugin for checking Redmine's own vulnerabilities
84
126
  email:
85
127
  - sho.hsmt@gmail.com
@@ -88,30 +130,38 @@ extensions: []
88
130
  extra_rdoc_files: []
89
131
  files:
90
132
  - ".gitignore"
133
+ - ".travis.yml"
91
134
  - LICENSE
92
135
  - README.md
93
136
  - Rakefile
94
137
  - app/controllers/.keep
95
138
  - app/helpers/.keep
139
+ - app/helpers/redmine_audit_helper.rb
96
140
  - app/models/.keep
97
141
  - app/models/mailer.rb
98
142
  - app/views/.keep
99
- - app/views/mailer/unfixed_advisories_found.html.erb
100
- - app/views/mailer/unfixed_advisories_found.text.erb
143
+ - app/views/mailer/unfixed_redmine_advisories_found.html.erb
144
+ - app/views/mailer/unfixed_redmine_advisories_found.text.erb
145
+ - app/views/mailer/unfixed_ruby_and_gems_advisories_found.html.erb
146
+ - app/views/mailer/unfixed_ruby_and_gems_advisories_found.text.erb
101
147
  - bin/console
102
148
  - bin/setup
103
149
  - config/locales/en.yml
104
150
  - config/locales/ja.yml
105
151
  - config/routes.rb
152
+ - data/plugin_advisories.yml
153
+ - data/plugins.yml
106
154
  - db/migrate/.keep
107
155
  - init.rb
108
156
  - lib/redmine_audit.rb
109
157
  - lib/redmine_audit/advisory.rb
110
158
  - lib/redmine_audit/database.rb
159
+ - lib/redmine_audit/plugin_database.rb
111
160
  - lib/redmine_audit/version.rb
112
161
  - lib/tasks/.keep
113
162
  - lib/tasks/redmine_audit.rake
114
163
  - redmine_audit.gemspec
164
+ - scripts/create_plugin_list
115
165
  homepage: https://github.com/sho-h/redmine_audit
116
166
  licenses:
117
167
  - MIT
@@ -132,7 +182,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
132
182
  version: '0'
133
183
  requirements: []
134
184
  rubyforge_project:
135
- rubygems_version: 2.5.2
185
+ rubygems_version: 2.5.2.3
136
186
  signing_key:
137
187
  specification_version: 4
138
188
  summary: Redmine plugin for checking Redmine's own vulnerabilities
@@ -1,27 +0,0 @@
1
- <div><%= l(:mail_summary_advisories_found) %></div>
2
-
3
- <%-
4
- @advisories.each do |advisory|
5
- if advisory.external_references && !advisory.external_references.empty?
6
- ext_refs = advisory.external_references
7
- else
8
- ext_refs = 'none'
9
- end
10
- solution = advisory.fixed_versions.join(', ')
11
- -%>
12
-
13
- <div>
14
- <ul style="list-style:none;">
15
- <li>Name: Redmine</li>
16
- <li>Version: <%= @redmine_version %></li>
17
- <li>Severity: <%= advisory.severity %></li>
18
- <li>URL: <%= ext_refs %></li>
19
- <li>Detail: <%= advisory.details %></li>
20
- <li>Solution: upgrade to <%= solution %></li>
21
- </ul>
22
- </div>
23
- <%- end -%>
24
-
25
- <div>
26
- <%= l(:mail_detail_link_head_advisories_found) %> <%= link_to 'redmine.org', RedmineAudit::Database::URL %> <%= l(:mail_detail_link_tail_advisories_found) %>
27
- </div>