redmine_audit 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +11 -0
- data/README.md +4 -4
- data/app/helpers/redmine_audit_helper.rb +18 -0
- data/app/models/mailer.rb +31 -14
- data/app/views/mailer/unfixed_redmine_advisories_found.html.erb +54 -0
- data/app/views/mailer/unfixed_redmine_advisories_found.text.erb +43 -0
- data/app/views/mailer/unfixed_ruby_and_gems_advisories_found.html.erb +33 -0
- data/app/views/mailer/unfixed_ruby_and_gems_advisories_found.text.erb +27 -0
- data/config/locales/en.yml +3 -3
- data/config/locales/ja.yml +3 -3
- data/data/plugin_advisories.yml +19 -0
- data/data/plugins.yml +2468 -0
- data/init.rb +2 -1
- data/lib/redmine_audit/advisory.rb +49 -7
- data/lib/redmine_audit/database.rb +1 -3
- data/lib/redmine_audit/plugin_database.rb +62 -0
- data/lib/redmine_audit/version.rb +1 -1
- data/lib/tasks/redmine_audit.rake +53 -15
- data/redmine_audit.gemspec +3 -0
- data/scripts/create_plugin_list +42 -0
- metadata +55 -5
- data/app/views/mailer/unfixed_advisories_found.html.erb +0 -27
- data/app/views/mailer/unfixed_advisories_found.text.erb +0 -21
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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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,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
|
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:
|
17
|
+
rake redmine:audit users="1,23, 56" RAILS_ENV="production"
|
13
18
|
END_DESC
|
14
19
|
|
15
20
|
namespace :redmine do
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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.
|
67
|
+
Mailer.unfixed_ruby_and_gems_advisories_found(ruby_result, gems_result, args.users).deliver
|
30
68
|
end
|
31
69
|
end
|
32
70
|
end
|
data/redmine_audit.gemspec
CHANGED
@@ -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.
|
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:
|
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/
|
100
|
-
- app/views/mailer/
|
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>
|