Browse Source

DIV-986: Add antivirus poc

pull/163/head
Steven Ly 5 years ago
parent
commit
fec6557680
9 changed files with 179 additions and 15 deletions
  1. +5
    -1
      .env.example
  2. +3
    -0
      .gitignore
  3. +12
    -0
      docker-compose.yml
  4. +47
    -0
      edivorce/apps/core/templates/poc/upload.html
  5. +9
    -1
      edivorce/apps/core/urls.py
  6. +44
    -0
      edivorce/apps/core/validators.py
  7. +23
    -0
      edivorce/apps/core/views/poc.py
  8. +10
    -0
      edivorce/settings/base.py
  9. +26
    -13
      requirements.txt

+ 5
- 1
.env.example View File

@ -1,9 +1,13 @@
DEBUG=True DEBUG=True
TEMPLATE_DEBUG=True TEMPLATE_DEBUG=True
DJANGO_SECRET_KEY=
DATABASE_ENGINE=django.db.backends.sqlite3 DATABASE_ENGINE=django.db.backends.sqlite3
DATABASE_NAME=db.sqlite3 DATABASE_NAME=db.sqlite3
DATABASE_USER= DATABASE_USER=
DATABASE_PASSWORD= DATABASE_PASSWORD=
DATABASE_HOST= DATABASE_HOST=
DATABASE_PORT= DATABASE_PORT=
# ClamAV settings
CLAMAV_ENABLED=True
CLAMAV_TCP_PORT=3310
CLAMAV_TCP_ADDR=localhost

+ 3
- 0
.gitignore View File

@ -31,6 +31,7 @@ var/
*.egg-info/ *.egg-info/
.installed.cfg .installed.cfg
*.egg *.egg
.python-version
# PyInstaller # PyInstaller
# Usually these files are written by a python script from a template # Usually these files are written by a python script from a template
@ -411,3 +412,5 @@ ASALocalRun/
# MFractors (Xamarin productivity tool) working folder # MFractors (Xamarin productivity tool) working folder
.mfractor/ .mfractor/
edivorce/share

+ 12
- 0
docker-compose.yml View File

@ -0,0 +1,12 @@
version: "3.7"
services:
# Antivirus
antivirus:
container_name: edivorce-antivirus
hostname: antivirus
build: https://github.com/bcgov/clamav.git
ports:
- "3310:3310"
restart: always

+ 47
- 0
edivorce/apps/core/templates/poc/upload.html View File

@ -0,0 +1,47 @@
{% extends 'base.html' %}
{% load input_field %}
{% load step_order %}
{% load load_json %}
{% block title %}{{ block.super }}: POC{% endblock %}
{% block progress %}{% include "partials/progress.html" %}{% endblock %}
{% block content %}
<h1><small>Proof of Concept:</small>File scanning</h1>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="question-well">
<h3>Upload a file to test virus scanning.</h3>
<div class="form-group {% if form.upload_file.errors %}has-error{% elif validation_success %}has-success{% endif %}">
<div>{{ form.upload_file }}</div>
{% if form.upload_file.errors %}
<span class="help-block">
{% for err in form.upload_file.errors %}{{ err }}{% endfor %}
</span>
{% endif %}
{% if validation_success %}<span class="help-block">No viruses found</span>{% endif %}
</div>
</div>
<div class="form-buttons clearfix">
<button type="submit" class="btn btn-primary pull-right">Submit</button>
</div>
</form>
{% endblock %}
{% block formbuttons %}
{% endblock %}
{% block sidebarNav %}
<!-- no sidebar -->
{% endblock %}
{% block sidebar %}
<!-- no sidebar -->
{% endblock %}

+ 9
- 1
edivorce/apps/core/urls.py View File

@ -1,6 +1,9 @@
from django.conf.urls import url from django.conf.urls import url
from django.conf import settings
from .views import main, system, pdf, api, localdev, poc
from .decorators import bceid_required
from .views import main, system, pdf, api, localdev
urlpatterns = [ urlpatterns = [
# url(r'^guide$', styleguide.guide), # url(r'^guide$', styleguide.guide),
@ -32,3 +35,8 @@ urlpatterns = [
url(r'^current$', system.current, name="current"), url(r'^current$', system.current, name="current"),
url(r'^$', main.home, name="home"), url(r'^$', main.home, name="home"),
] ]
if settings.DEBUG:
urlpatterns = urlpatterns + [
url(r'poc/upload', bceid_required(poc.UploadScan.as_view()), name="poc-upload"),
]

+ 44
- 0
edivorce/apps/core/validators.py View File

@ -0,0 +1,44 @@
import logging
import clamd
import sys
from django.core.exceptions import ValidationError
from django.conf import settings
logger = logging.getLogger(__name__)
def file_scan_validation(file):
"""
This validator sends the file to ClamAV for scanning and returns returns to the form. By default, if antivirus
service is not available or there are errors, the validation will fail.
Usage:
class UploadForm(forms.Form):
file = forms.FileField(validators=[file_scan_validation])
:param file:
:return:
"""
logger.debug("starting file scanning with clamav")
if not settings.CLAMAV_ENABLED:
logger.warning('File scanning has been disabled.')
return
# make sure we're at the beginning of the file stream
file.seek(0)
# we're just going to assume a network connection to clamav here .. no local unix socket support
scanner = clamd.ClamdNetworkSocket(settings.CLAMAV_TCP_ADDR, settings.CLAMAV_TCP_PORT)
try:
result = scanner.instream(file)
except:
# it doesn't really matter what the actual error is .. log it and raise validation error
logger.error('Error occurred while trying to scan file. "{}"'.format(sys.exc_info()[0]))
raise ValidationError('Unable to scan file.', code='scanerror')
finally:
# reset file stream
file.seek(0)
if result and result['stream'][0] == 'FOUND':
logger.warning('Virus found: {}'.format(file.name))
raise ValidationError('Infected file found.', code='infected')

+ 23
- 0
edivorce/apps/core/views/poc.py View File

@ -0,0 +1,23 @@
from django.shortcuts import render
from django.views.generic.edit import FormView
from django import forms
from ..validators import file_scan_validation
"""
Everything in this file is considered as proof of concept work and should not be used for production code.
"""
class UploadForm(forms.Form):
upload_file = forms.FileField(validators=[file_scan_validation])
class UploadScan(FormView):
form_class = UploadForm
template_name = "poc/upload.html"
def form_valid(self, form):
context = self.get_context_data()
context['validation_success'] = True
return render(self.request, self.template_name, context)

+ 10
- 0
edivorce/settings/base.py View File

@ -11,8 +11,12 @@ https://docs.djangoproject.com/en/1.8/ref/settings/
""" """
import os import os
from environs import Env
from unipath import Path from unipath import Path
env = Env()
env.read_env() # read .env file, if it exists
# Build paths inside the project like this: os.path.join(BASE_DIR, ...) # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
PROJECT_ROOT = Path(__file__).parent.parent.parent PROJECT_ROOT = Path(__file__).parent.parent.parent
BASE_DIR = Path(__file__).parent.parent BASE_DIR = Path(__file__).parent.parent
@ -145,3 +149,9 @@ DEBUG_TOOLBAR_CONFIG = {
SECURE_BROWSER_XSS_FILTER = True SECURE_BROWSER_XSS_FILTER = True
LOGOUT_URL = '/accounts/logout/' LOGOUT_URL = '/accounts/logout/'
# CLAMAV settings
CLAMAV_ENABLED = env.bool('CLAMAV_ENABLED', True)
CLAMAV_TCP_PORT = env.int('CLAMAV_TCP_PORT', 3310)
CLAMAV_TCP_ADDR = env('CLAMAV_TCP_ADDR', 'localhost')

+ 26
- 13
requirements.txt View File

@ -1,13 +1,26 @@
Django>=2.2,<3.0
django-compressor>=2.4,<3.0
django-crispy-forms>=1.9,<2.0
django-debug-toolbar>=2.2,<3.0
django-sass-processor>=0.8,<1.0
djangorestframework>=3.11,<4.0
gunicorn>=20.0,<21.0
libsass>=0.20.0<1.0
psycopg2>=2.8,<3.0
requests>=2.24,<3.0
six>=1.15,<2.0
Unipath>=1.1,<2.0
whitenoise>=3.3.1,<4.0
certifi==2020.6.20
chardet==3.0.4
clamd==1.0.2
Django==2.2.15
django-appconf==1.0.4
django-compressor==2.4
django-crispy-forms==1.9.2
django-debug-toolbar==2.2
django-sass-processor==0.8
djangorestframework==3.11.1
environs==8.0.0
gunicorn==20.0.4
idna==2.10
libsass==0.20.0
marshmallow==3.7.1
psycopg2==2.8.5
python-dotenv==0.14.0
pytz==2020.1
rcssmin==1.0.6
requests==2.24.0
rjsmin==1.1.0
six==1.15.0
sqlparse==0.3.1
Unipath==1.1
urllib3==1.25.10
whitenoise==3.3.1

Loading…
Cancel
Save