Skip to content

Commit 1fb34ad

Browse files
authored
Implement a ScanForVirus Pipeline #1182 (#1193)
Signed-off-by: tdruez <tdruez@nexb.com>
1 parent 07b48c0 commit 1fb34ad

File tree

9 files changed

+187
-0
lines changed

9 files changed

+187
-0
lines changed

CHANGELOG.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ Changelog
44
v34.6.0 (unreleased)
55
--------------------
66

7+
- Add a new ``scan_for_virus`` add-on pipeline based on ClamAV scan.
8+
https://github.com/nexB/scancode.io/issues/1182
9+
710
- Add ability to filter by tag on the resource list view.
811
https://github.com/nexB/scancode.io/issues/1217
912

docker-compose.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,18 @@ services:
6666
- web
6767
restart: always
6868

69+
clamav:
70+
image: clamav/clamav
71+
volumes:
72+
- clamav_data:/var/lib/clamav
73+
- workspace:/var/scancodeio/workspace/
74+
ports:
75+
- "3310:3310"
76+
restart: always
77+
6978
volumes:
7079
db_data:
7180
redis_data:
81+
clamav_data:
7282
static:
7383
workspace:

docs/built-in-pipelines.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,14 @@ Scan Codebase
182182
:members:
183183
:member-order: bysource
184184

185+
.. _pipeline_scan_for_virus:
186+
187+
Scan For Virus
188+
--------------
189+
.. autoclass:: scanpipe.pipelines.scan_for_virus.ScanForVirus()
190+
:members:
191+
:member-order: bysource
192+
185193
.. _pipeline_scan_single_package:
186194

187195
Scan Single Package

docs/scanpipe-pipes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ Generic
88
.. automodule:: scanpipe.pipes
99
:members:
1010

11+
ClamAV
12+
------
13+
.. automodule:: scanpipe.pipes.clamav
14+
:members:
15+
1116
Codebase
1217
--------
1318
.. automodule:: scanpipe.pipes.codebase

scancodeio/settings.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,10 @@
364364
for queue_config in RQ_QUEUES.values():
365365
queue_config["ASYNC"] = False
366366

367+
# ClamAV virus scan
368+
CLAMD_USE_TCP = env.bool("CLAMD_USE_TCP", default=True)
369+
CLAMD_TCP_ADDR = env.str("CLAMD_TCP_ADDR", default="clamav")
370+
367371
# Django restframework
368372

369373
REST_FRAMEWORK = {

scanpipe/pipelines/scan_for_virus.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
#
3+
# http://nexb.com and https://github.com/nexB/scancode.io
4+
# The ScanCode.io software is licensed under the Apache License version 2.0.
5+
# Data generated with ScanCode.io is provided as-is without warranties.
6+
# ScanCode is a trademark of nexB Inc.
7+
#
8+
# You may not use this software except in compliance with the License.
9+
# You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0
10+
# Unless required by applicable law or agreed to in writing, software distributed
11+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
12+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
13+
# specific language governing permissions and limitations under the License.
14+
#
15+
# Data Generated with ScanCode.io is provided on an "AS IS" BASIS, WITHOUT WARRANTIES
16+
# OR CONDITIONS OF ANY KIND, either express or implied. No content created from
17+
# ScanCode.io should be considered or used as legal advice. Consult an Attorney
18+
# for any legal advice.
19+
#
20+
# ScanCode.io is a free software code scanning tool from nexB Inc. and others.
21+
# Visit https://github.com/nexB/scancode.io for support and download.
22+
23+
from scanpipe.pipelines import Pipeline
24+
from scanpipe.pipes import clamav
25+
26+
27+
class ScanForVirus(Pipeline):
28+
"""Run a ClamAV scan on the codebase directory to detect virus infection."""
29+
30+
download_inputs = False
31+
is_addon = True
32+
33+
@classmethod
34+
def steps(cls):
35+
return (cls.scan_for_virus,)
36+
37+
def scan_for_virus(self):
38+
"""Run a ClamAV scan to detect virus infection."""
39+
clamav.scan_for_virus(self.project)

scanpipe/pipes/clamav.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
#
3+
# http://nexb.com and https://github.com/nexB/scancode.io
4+
# The ScanCode.io software is licensed under the Apache License version 2.0.
5+
# Data generated with ScanCode.io is provided as-is without warranties.
6+
# ScanCode is a trademark of nexB Inc.
7+
#
8+
# You may not use this software except in compliance with the License.
9+
# You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0
10+
# Unless required by applicable law or agreed to in writing, software distributed
11+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
12+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
13+
# specific language governing permissions and limitations under the License.
14+
#
15+
# Data Generated with ScanCode.io is provided on an "AS IS" BASIS, WITHOUT WARRANTIES
16+
# OR CONDITIONS OF ANY KIND, either express or implied. No content created from
17+
# ScanCode.io should be considered or used as legal advice. Consult an Attorney
18+
# for any legal advice.
19+
#
20+
# ScanCode.io is a free software code scanning tool from nexB Inc. and others.
21+
# Visit https://github.com/nexB/scancode.io for support and download.
22+
23+
from pathlib import Path
24+
25+
from django.conf import settings
26+
27+
import clamd
28+
29+
30+
def scan_for_virus(project):
31+
"""
32+
Run a ClamAV scan to detect virus infection.
33+
Create one Project error message per found virus.
34+
"""
35+
if settings.CLAMD_USE_TCP:
36+
clamd_socket = clamd.ClamdNetworkSocket(settings.CLAMD_TCP_ADDR)
37+
else:
38+
clamd_socket = clamd.ClamdUnixSocket()
39+
40+
try:
41+
scan_response = clamd_socket.multiscan(file=str(project.codebase_path))
42+
except clamd.ClamdError as e:
43+
raise Exception(f"Error with the ClamAV service: {e}")
44+
45+
for resource_location, results in scan_response.items():
46+
status, reason = results
47+
resource_path = Path(resource_location).relative_to(project.codebase_path)
48+
details = {
49+
"status": status,
50+
"reason": reason,
51+
"resource_path": str(resource_path),
52+
}
53+
project.add_error(
54+
description="Virus detected",
55+
model="ScanForVirus",
56+
details=details,
57+
)

scanpipe/tests/pipes/test_clamav.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
#
3+
# http://nexb.com and https://github.com/nexB/scancode.io
4+
# The ScanCode.io software is licensed under the Apache License version 2.0.
5+
# Data generated with ScanCode.io is provided as-is without warranties.
6+
# ScanCode is a trademark of nexB Inc.
7+
#
8+
# You may not use this software except in compliance with the License.
9+
# You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0
10+
# Unless required by applicable law or agreed to in writing, software distributed
11+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
12+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
13+
# specific language governing permissions and limitations under the License.
14+
#
15+
# Data Generated with ScanCode.io is provided on an "AS IS" BASIS, WITHOUT WARRANTIES
16+
# OR CONDITIONS OF ANY KIND, either express or implied. No content created from
17+
# ScanCode.io should be considered or used as legal advice. Consult an Attorney
18+
# for any legal advice.
19+
#
20+
# ScanCode.io is a free software code scanning tool from nexB Inc. and others.
21+
# Visit https://github.com/nexB/scancode.io for support and download.
22+
23+
from pathlib import Path
24+
from unittest import mock
25+
26+
from django.test import TestCase
27+
28+
from scanpipe.models import Project
29+
from scanpipe.pipes import clamav
30+
from scanpipe.tests import make_resource_file
31+
32+
33+
class ScanPipeClamAVPipesTest(TestCase):
34+
data_location = Path(__file__).parent.parent / "data"
35+
36+
@mock.patch("clamd.ClamdNetworkSocket.multiscan")
37+
def test_scanpipe_pipes_clamav_scan_for_virus(self, mock_multiscan):
38+
project = Project.objects.create(name="project")
39+
r1 = make_resource_file(project=project, path="eicar.zip")
40+
r2 = make_resource_file(project=project, path="eicar.zip-extract/eicar.com")
41+
42+
mock_multiscan.return_value = {
43+
r1.location: ("FOUND", "Win.Test.EICAR_HDB-1"),
44+
r2.location: ("FOUND", "Win.Test.EICAR_HDB-1"),
45+
}
46+
47+
clamav.scan_for_virus(project)
48+
self.assertEqual(2, len(project.projectmessages.all()))
49+
error_message = project.projectmessages.all()[0]
50+
self.assertEqual("error", error_message.severity)
51+
self.assertEqual("Virus detected", error_message.description)
52+
self.assertEqual("ScanForVirus", error_message.model)
53+
expected_details = {
54+
"reason": "Win.Test.EICAR_HDB-1",
55+
"status": "FOUND",
56+
"resource_path": "eicar.zip",
57+
}
58+
self.assertEqual(expected_details, error_message.details)

setup.cfg

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ install_requires =
102102
# Markdown
103103
markdown-it-py==3.0.0
104104
bleach==6.1.0
105+
# Antivirus
106+
clamd==1.0.2
105107

106108
[options.extras_require]
107109
dev =
@@ -146,6 +148,7 @@ scancodeio_pipelines =
146148
populate_purldb = scanpipe.pipelines.populate_purldb:PopulatePurlDB
147149
resolve_dependencies = scanpipe.pipelines.resolve_dependencies:ResolveDependencies
148150
scan_codebase = scanpipe.pipelines.scan_codebase:ScanCodebase
151+
scan_for_virus = scanpipe.pipelines.scan_for_virus:ScanForVirus
149152
scan_single_package = scanpipe.pipelines.scan_single_package:ScanSinglePackage
150153

151154
[isort]

0 commit comments

Comments
 (0)