ratonvirus 0.1.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 +7 -0
- data/CHANGELOG.md +3 -0
- data/LICENSE +22 -0
- data/README.md +247 -0
- data/app/validators/antivirus_validator.rb +26 -0
- data/config/locales/en.yml +6 -0
- data/config/locales/fi.yml +6 -0
- data/config/locales/sv.yml +6 -0
- data/lib/ratonvirus.rb +109 -0
- data/lib/ratonvirus/engine.rb +6 -0
- data/lib/ratonvirus/error.rb +7 -0
- data/lib/ratonvirus/processable.rb +17 -0
- data/lib/ratonvirus/scanner/addon/remove_infected.rb +18 -0
- data/lib/ratonvirus/scanner/base.rb +154 -0
- data/lib/ratonvirus/scanner/eicar.rb +34 -0
- data/lib/ratonvirus/scanner/support/callbacks.rb +84 -0
- data/lib/ratonvirus/storage/active_storage.rb +96 -0
- data/lib/ratonvirus/storage/base.rb +56 -0
- data/lib/ratonvirus/storage/carrierwave.rb +36 -0
- data/lib/ratonvirus/storage/filepath.rb +46 -0
- data/lib/ratonvirus/storage/multi.rb +78 -0
- data/lib/ratonvirus/support/backend.rb +181 -0
- data/lib/ratonvirus/version.rb +5 -0
- data/lib/tasks/ratonvirus.rake +40 -0
- metadata +179 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2858dfe945bf74c96269185692c5737c5469adf2404f144aeb4f04e9ae546687
|
4
|
+
data.tar.gz: 07262f10a07c229a92fa8e0b7f7747cff8f33d538804048706b0bd60650d8aeb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0f039177a939be1eb63a0fc00c626b533dffafae9c906c607e22bf968247e602380304e6075b636d3d08daaa98815adaca5cafa8d3cf87572fd232a15cb3c1c1
|
7
|
+
data.tar.gz: 041f921088ba88398efddd3d437a38360435b88b4a0ab424047768c59b3ecabda69ca1597a5e0a6ac555a818946713b18492597858b29c167d755c3811ccafb4
|
data/CHANGELOG.md
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2018 Mainio Tech Ltd.
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,247 @@
|
|
1
|
+
# <img alt="Ratonvirus" src="https://raw.githubusercontent.com/mainio/ratonvirus/master/ratonvirus.png" width="600">
|
2
|
+
|
3
|
+
Rails antivirus made easy.
|
4
|
+
Developed by [Mainio Tech](https://www.mainiotech.fi/).
|
5
|
+
|
6
|
+
[](https://travis-ci.org/mainio/ratonvirus)
|
7
|
+
[](https://codecov.io/gh/mainio/ratonvirus)
|
8
|
+
|
9
|
+
Ratonvirus allows your Rails application to rat on the viruses that your users
|
10
|
+
try to upload to your site. This works through Rails validators that you can
|
11
|
+
easily attach to your models.
|
12
|
+
|
13
|
+
The purpose of Ratonvirus is to act as the glue that binds your Rails
|
14
|
+
application to a virus scanner and file storage engine of your choise.
|
15
|
+
|
16
|
+
Setting up scanning for files attached to models:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
class Model < ApplicationRecord
|
20
|
+
# ...
|
21
|
+
validates :file, antivirus: true
|
22
|
+
# ...
|
23
|
+
end
|
24
|
+
```
|
25
|
+
|
26
|
+
Running manual scans:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
puts "File contains a virus" if Ratonvirus.scanner.virus?('/path/to/file.pdf')
|
30
|
+
```
|
31
|
+
|
32
|
+
Manual scanning works e.g. for file uploads, file object and file paths.
|
33
|
+
|
34
|
+
Ratonvirus works well with Active Storage out of the box. Support for
|
35
|
+
CarrierWave is provided through an additional gem (see details well).
|
36
|
+
|
37
|
+
## When to use this?
|
38
|
+
|
39
|
+
This gem can be handy if you want to:
|
40
|
+
|
41
|
+
- Scan files for viruses in Ruby, especially in Rails
|
42
|
+
- Make the scanning logic agnostic of the
|
43
|
+
* scanner implementation (e.g. ClamAV)
|
44
|
+
* file storage (e.g. Active Storage, CarrierWave, no storage engine)
|
45
|
+
- Separate testing code for the scanner implementation or the file storage
|
46
|
+
cleanly into its own place.
|
47
|
+
|
48
|
+
## Prerequisites
|
49
|
+
|
50
|
+
There is no fully Ruby-based virus scanner available on the market for a good
|
51
|
+
reason: it is a heavy task. Therefore, this gem may not work as automagically
|
52
|
+
as you are used to with many other gems.
|
53
|
+
|
54
|
+
You will need to setup a virus scanner on your machine. If you have done that
|
55
|
+
before, configuration should be rather simple. Instructions are provided for
|
56
|
+
the open source ClamAV scanner.
|
57
|
+
|
58
|
+
This gem ships with an example
|
59
|
+
[EICAR file](https://en.wikipedia.org/wiki/EICAR_test_file) scanner to test out
|
60
|
+
the configuration process. This scanner allows you to test the functionality of
|
61
|
+
this gem with no external requirements but it should not be used in production
|
62
|
+
environments.
|
63
|
+
|
64
|
+
## Installation
|
65
|
+
|
66
|
+
Add this line to your application's Gemfile:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
gem 'ratonvirus'
|
70
|
+
```
|
71
|
+
|
72
|
+
And then execute:
|
73
|
+
|
74
|
+
```bash
|
75
|
+
$ bundle
|
76
|
+
```
|
77
|
+
|
78
|
+
Add this initializer to your application's `config/initializers/ratonvirus.rb`:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
# config/initializers/ratonvirus.rb
|
82
|
+
Ratonvirus.configure do |config|
|
83
|
+
config.scanner = :eicar
|
84
|
+
config.storage = :active_storage
|
85
|
+
end
|
86
|
+
```
|
87
|
+
|
88
|
+
After installation, test that the gem is loaded properly in your it is ready to
|
89
|
+
do a sample validation:
|
90
|
+
|
91
|
+
```bash
|
92
|
+
$ bundle exec rails ratonvirus:test
|
93
|
+
```
|
94
|
+
|
95
|
+
This command should show the following message when correctly installed:
|
96
|
+
|
97
|
+
```
|
98
|
+
Ratonvirus correctly configured.
|
99
|
+
```
|
100
|
+
|
101
|
+
**NOTE:**
|
102
|
+
|
103
|
+
By default Ratonvirus is set to remove all infected files that it detects after
|
104
|
+
scanning them. If you want to remove this functionality, please refer to the
|
105
|
+
[Scanner addons](doc/index.md#scanner-addons) section of the developer
|
106
|
+
documentation.
|
107
|
+
|
108
|
+
## Usage
|
109
|
+
|
110
|
+
### Applying the antivirus validator to your models
|
111
|
+
|
112
|
+
In order to apply the antivirus validator, you need to have your file uploads
|
113
|
+
handled by a storage backend such as Active Storage. Examples are provided below
|
114
|
+
for the most common options.
|
115
|
+
|
116
|
+
#### Example with Active Storage
|
117
|
+
|
118
|
+
Your model should look similar to this:
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
class YourModel < ApplicationRecord
|
122
|
+
has_one_attached :file
|
123
|
+
validates :file, antivirus: true # Add this for antivirus validation
|
124
|
+
end
|
125
|
+
```
|
126
|
+
|
127
|
+
#### Example with CarrierWave
|
128
|
+
|
129
|
+
Your model should look similar to this:
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
class YourModel < ApplicationRecord
|
133
|
+
mount_uploader :file, YourUploader
|
134
|
+
validates :file, antivirus: true # Add this for antivirus validation
|
135
|
+
end
|
136
|
+
```
|
137
|
+
|
138
|
+
Please note the extra configuration you need for CarrierWave from the
|
139
|
+
configuration examples.
|
140
|
+
|
141
|
+
### Manually checking for viruses
|
142
|
+
|
143
|
+
For the manual scans to work you need to configure the correct storage backend
|
144
|
+
first that accepts these resources:
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
# config/initializers/ratonvirus.rb
|
148
|
+
|
149
|
+
# When scanning files or file paths only
|
150
|
+
Ratonvirus.configure do |config|
|
151
|
+
config.storage = :filepath
|
152
|
+
end
|
153
|
+
|
154
|
+
# When scanning files, file paths or active storage resources
|
155
|
+
Ratonvirus.configure do |config|
|
156
|
+
config.storage = :multi, [:filepath, :active_storage]
|
157
|
+
end
|
158
|
+
```
|
159
|
+
|
160
|
+
In case you want to manually scan for viruses, you can use any of the following
|
161
|
+
examples:
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
# It is a good idea to check first that the scanner is available.
|
165
|
+
if Ratonvirus.scanner.available?
|
166
|
+
# Scanning a file path
|
167
|
+
path = '/path/to/file.pdf'
|
168
|
+
if Ratonvirus.scanner.virus?(path)
|
169
|
+
puts "There is a virus at #{path}"
|
170
|
+
end
|
171
|
+
|
172
|
+
# Scanning a File object
|
173
|
+
file = File.new(path)
|
174
|
+
if Ratonvirus.scanner.virus?(file)
|
175
|
+
puts "There is a virus at #{file.path}"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
```
|
179
|
+
|
180
|
+
### Sample configurations
|
181
|
+
|
182
|
+
Here are few sample configurations to speed up the configuration process.
|
183
|
+
|
184
|
+
#### Active Storage and ClamAV
|
185
|
+
|
186
|
+
Gemfile:
|
187
|
+
|
188
|
+
```ruby
|
189
|
+
gem 'ratonvirus'
|
190
|
+
gem 'ratonvirus-clamby'
|
191
|
+
```
|
192
|
+
|
193
|
+
Initializer:
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
# config/initializers/ratonvirus.rb
|
197
|
+
Ratonvirus.configure do |config|
|
198
|
+
config.scanner = :clamby
|
199
|
+
config.storage = :active_storage
|
200
|
+
end
|
201
|
+
```
|
202
|
+
|
203
|
+
Model:
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
class YourModel < ApplicationRecord
|
207
|
+
has_one_attached :file
|
208
|
+
validates :file, antivirus: true
|
209
|
+
end
|
210
|
+
```
|
211
|
+
|
212
|
+
#### CarrierWave and ClamAV
|
213
|
+
|
214
|
+
Gemfile:
|
215
|
+
|
216
|
+
```ruby
|
217
|
+
gem 'ratonvirus'
|
218
|
+
gem 'ratonvirus-clamby'
|
219
|
+
```
|
220
|
+
|
221
|
+
Initializer:
|
222
|
+
|
223
|
+
```ruby
|
224
|
+
# config/initializers/ratonvirus.rb
|
225
|
+
Ratonvirus.configure do |config|
|
226
|
+
config.scanner = :clamby
|
227
|
+
config.storage = :carrierwave
|
228
|
+
end
|
229
|
+
```
|
230
|
+
|
231
|
+
Model:
|
232
|
+
|
233
|
+
```ruby
|
234
|
+
class YourModel < ApplicationRecord
|
235
|
+
mount_uploader :file, YourUploader
|
236
|
+
validates :file, antivirus: true
|
237
|
+
end
|
238
|
+
```
|
239
|
+
|
240
|
+
## Further configuration and development
|
241
|
+
|
242
|
+
For further information about the configurations and how to create custom
|
243
|
+
scanners, please refer to the [documentation](doc/index.md).
|
244
|
+
|
245
|
+
## License
|
246
|
+
|
247
|
+
MIT, see [LICENSE](LICENSE).
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class AntivirusValidator < ActiveModel::EachValidator
|
4
|
+
def validate_each(record, attribute, value)
|
5
|
+
# Avoid unnecessary scans in case
|
6
|
+
# a) the storage backend does not accept the resource
|
7
|
+
# b) the attribute has not changed
|
8
|
+
storage = Ratonvirus.storage
|
9
|
+
return unless storage.accept?(value)
|
10
|
+
return unless storage.changed?(record, attribute)
|
11
|
+
|
12
|
+
# Only scan if the scanner is available
|
13
|
+
scanner = Ratonvirus.scanner
|
14
|
+
return unless scanner.available?
|
15
|
+
|
16
|
+
if scanner.virus?(value)
|
17
|
+
if scanner.errors.any?
|
18
|
+
scanner.errors.each do |err|
|
19
|
+
record.errors.add attribute, err
|
20
|
+
end
|
21
|
+
else
|
22
|
+
record.errors.add attribute, :antivirus_virus_detected
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/ratonvirus.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require "active_support"
|
3
|
+
require "digest"
|
4
|
+
|
5
|
+
require_relative "ratonvirus/version"
|
6
|
+
require_relative "ratonvirus/error"
|
7
|
+
require_relative "ratonvirus/processable"
|
8
|
+
require_relative "ratonvirus/support/backend"
|
9
|
+
require_relative "ratonvirus/scanner/support/callbacks"
|
10
|
+
require_relative "ratonvirus/scanner/base"
|
11
|
+
require_relative "ratonvirus/scanner/eicar"
|
12
|
+
require_relative "ratonvirus/scanner/addon/remove_infected"
|
13
|
+
require_relative "ratonvirus/storage/base"
|
14
|
+
require_relative "ratonvirus/storage/filepath"
|
15
|
+
require_relative "ratonvirus/storage/active_storage"
|
16
|
+
require_relative "ratonvirus/storage/carrierwave"
|
17
|
+
require_relative "ratonvirus/storage/multi"
|
18
|
+
|
19
|
+
require_relative "ratonvirus/engine" if defined?(Rails)
|
20
|
+
|
21
|
+
module Ratonvirus
|
22
|
+
extend Ratonvirus::Support::Backend
|
23
|
+
|
24
|
+
# Usage:
|
25
|
+
# Ratonvirus.configure do |config|
|
26
|
+
# config.scanner = :eicar
|
27
|
+
# config.storage = :active_storage
|
28
|
+
# config.addons = [:remove_infected]
|
29
|
+
# end
|
30
|
+
def self.configure
|
31
|
+
yield self
|
32
|
+
end
|
33
|
+
|
34
|
+
# Usage (set):
|
35
|
+
# Ratonvirus.scanner = :eicar
|
36
|
+
# Ratonvirus.scanner = :eicar, {option: 'value'}
|
37
|
+
# Ratonvirus.scanner = Ratonvirus::Scanner::Eicar.new
|
38
|
+
# Ratonvirus.scanner = Ratonvirus::Scanner::Eicar.new({option: 'value'})
|
39
|
+
#
|
40
|
+
# Usage (get):
|
41
|
+
# Ratonvirus.scanner
|
42
|
+
#
|
43
|
+
# Usage (destroy):
|
44
|
+
# Ratonvirus.destroy_scanner
|
45
|
+
define_backend :scanner, 'Scanner'
|
46
|
+
|
47
|
+
# Usage (set):
|
48
|
+
# Ratonvirus.storage = :active_storage
|
49
|
+
# Ratonvirus.storage = :active_storage, {option: 'value'}
|
50
|
+
# Ratonvirus.storage = Ratonvirus::Storage::ActiveStorage.new
|
51
|
+
# Ratonvirus.storage = Ratonvirus::Storage::ActiveStorage.new({option: 'value'})
|
52
|
+
#
|
53
|
+
# Usage (get):
|
54
|
+
# Ratonvirus.storage
|
55
|
+
#
|
56
|
+
# Usage (destroy):
|
57
|
+
# Ratonvirus.destroy_storage
|
58
|
+
define_backend :storage, 'Storage'
|
59
|
+
|
60
|
+
# Resets Ratonvirus to its initial state and configuration
|
61
|
+
def self.reset
|
62
|
+
# Default addons
|
63
|
+
@addons = [
|
64
|
+
ActiveSupport::Inflector.constantize(
|
65
|
+
"#{self.name}::Scanner::Addon::RemoveInfected"
|
66
|
+
),
|
67
|
+
]
|
68
|
+
|
69
|
+
self.destroy_scanner
|
70
|
+
self.destroy_storage
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.addons
|
74
|
+
@addons
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.addons=(addons)
|
78
|
+
@addons = []
|
79
|
+
addons.each do |addon|
|
80
|
+
self.add_addon addon
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.add_addon(addon)
|
85
|
+
addon_cls = addon_class(addon)
|
86
|
+
unless @addons.include?(addon_cls)
|
87
|
+
@addons << addon_cls
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.remove_addon(addon)
|
92
|
+
addon_cls = addon_class(addon)
|
93
|
+
@addons.delete(addon_cls)
|
94
|
+
end
|
95
|
+
|
96
|
+
# private
|
97
|
+
def self.addon_class(type)
|
98
|
+
return type if type.is_a?(Class)
|
99
|
+
|
100
|
+
subclass = ActiveSupport::Inflector.camelize(type.to_s)
|
101
|
+
ActiveSupport::Inflector.constantize(
|
102
|
+
"#{self.name}::Scanner::Addon::#{subclass}"
|
103
|
+
)
|
104
|
+
end
|
105
|
+
private_class_method :addon_class
|
106
|
+
|
107
|
+
# Reset the utility to start with
|
108
|
+
reset
|
109
|
+
end
|