Upload-collector

Upload-collector
This commit is contained in:
nu11secur1ty 2024-06-05 10:52:53 +03:00
parent 962ce84395
commit cf05cd6abd
1864 changed files with 155836 additions and 1 deletions

3
collector/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
.idea
__pycache__
HoneyPotCollector/settings.py

View file

View file

@ -0,0 +1,11 @@
from django.contrib import admin
from .models import HoneyPotServer
# Register your models here.
@admin.register(HoneyPotServer)
class HoneyPotServerAdmin(admin.ModelAdmin):
ordering = ['id']
list_display = ('name', 'ip', 'isActive', 'last_input')

View file

@ -0,0 +1,6 @@
from django.apps import AppConfig
class CollectorapiConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'CollectorAPI'

View file

@ -0,0 +1,58 @@
# Generated by Django 3.2.7 on 2021-09-30 11:17
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='HenoypotInfoHourlyStatisticsAttackIPs',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('timestamp', models.DateTimeField()),
('src_ip', models.CharField(max_length=45)),
('count', models.IntegerField()),
],
),
migrations.CreateModel(
name='HoneyPotServer',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=32)),
('description', models.CharField(max_length=65500)),
('ip', models.CharField(max_length=15, unique=True)),
('key', models.CharField(help_text='Enter 0000 to autogenerate key', max_length=64, unique=True)),
('last_input', models.DateTimeField(auto_now=True)),
('isActive', models.BooleanField(default=True)),
],
),
migrations.CreateModel(
name='HoneypotInfo',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('shard_id', models.CharField(max_length=32)),
('src_ip', models.CharField(max_length=45)),
('dst_ip', models.CharField(max_length=45)),
('ip_rep', models.CharField(max_length=128, null=True)),
('protocol', models.CharField(max_length=45, null=True)),
('type', models.CharField(max_length=128)),
('eventid', models.CharField(max_length=128, null=True)),
('event_type', models.CharField(max_length=128, null=True)),
('countryISO', models.CharField(max_length=4, null=True)),
('event_timestamp', models.DateTimeField()),
('recieved_timestamp', models.DateTimeField(auto_now=True)),
('raw_entry', models.JSONField()),
('server_id', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='CollectorAPI.honeypotserver')),
],
options={
'unique_together': {('shard_id', 'event_timestamp')},
},
),
]

View file

@ -0,0 +1,17 @@
# Generated by Django 3.2.7 on 2021-10-11 15:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('CollectorAPI', '0001_initial'),
]
operations = [
migrations.AddIndex(
model_name='honeypotinfo',
index=models.Index(fields=['event_timestamp'], name='CollectorAP_event_t_38766e_idx'),
),
]

View file

@ -0,0 +1,43 @@
# Generated by Django 3.2.7 on 2021-10-17 09:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('CollectorAPI', '0002_honeypotinfo_collectorap_event_t_38766e_idx'),
]
operations = [
migrations.AddField(
model_name='honeypotinfo',
name='city_name',
field=models.CharField(max_length=128, null=True),
),
migrations.AddField(
model_name='honeypotinfo',
name='continent_code',
field=models.CharField(max_length=2, null=True),
),
migrations.AddField(
model_name='honeypotinfo',
name='latitude',
field=models.DecimalField(decimal_places=6, max_digits=9, null=True),
),
migrations.AddField(
model_name='honeypotinfo',
name='longitude',
field=models.DecimalField(decimal_places=6, max_digits=9, null=True),
),
migrations.AddField(
model_name='honeypotinfo',
name='region_code',
field=models.CharField(max_length=4, null=True),
),
migrations.AddField(
model_name='honeypotinfo',
name='region_name',
field=models.CharField(max_length=128, null=True),
),
]

View file

@ -0,0 +1,22 @@
# Generated by Django 3.2.7 on 2021-10-19 16:41
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('CollectorAPI', '0003_auto_20211017_1242'),
]
operations = [
migrations.CreateModel(
name='HoneypotRawData',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('raw_entry', models.JSONField()),
('record', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to='CollectorAPI.honeypotinfo')),
],
),
]

View file

@ -0,0 +1,17 @@
# Generated by Django 3.2.7 on 2021-10-20 13:41
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('CollectorAPI', '0004_honeypotrawdata'),
]
operations = [
migrations.RemoveField(
model_name='honeypotinfo',
name='raw_entry',
),
]

View file

@ -0,0 +1,40 @@
# Generated by Django 3.2.7 on 2021-10-20 18:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('CollectorAPI', '0005_remove_honeypotinfo_raw_entry'),
]
operations = [
migrations.CreateModel(
name='HoneypotAgregate24hIps',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ip', models.CharField(max_length=45)),
('country_iso', models.CharField(max_length=4, null=True)),
('count', models.IntegerField()),
],
),
migrations.CreateModel(
name='HoneypotAgregate30DIps',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ip', models.CharField(max_length=45)),
('country_iso', models.CharField(max_length=4, null=True)),
('count', models.IntegerField()),
],
),
migrations.CreateModel(
name='HoneypotAgregate7DIps',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ip', models.CharField(max_length=45)),
('country_iso', models.CharField(max_length=4, null=True)),
('count', models.IntegerField()),
],
),
]

View file

@ -0,0 +1,37 @@
# Generated by Django 3.2.7 on 2021-10-20 20:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('CollectorAPI', '0006_honeypotagregate24hips_honeypotagregate30dips_honeypotagregate7dips'),
]
operations = [
migrations.CreateModel(
name='HoneypotAgregate24hCountry',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('country_iso', models.CharField(max_length=4, null=True)),
('count', models.IntegerField()),
],
),
migrations.CreateModel(
name='HoneypotAgregate30DCountry',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('country_iso', models.CharField(max_length=4, null=True)),
('count', models.IntegerField()),
],
),
migrations.CreateModel(
name='HoneypotAgregate7DCountry',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('country_iso', models.CharField(max_length=4, null=True)),
('count', models.IntegerField()),
],
),
]

View file

@ -0,0 +1,21 @@
# Generated by Django 3.2.7 on 2021-10-20 21:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('CollectorAPI', '0007_honeypotagregate24hcountry_honeypotagregate30dcountry_honeypotagregate7dcountry'),
]
operations = [
migrations.CreateModel(
name='HoneypotAgregatePerServer',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('data_id', models.CharField(max_length=45)),
('data', models.JSONField()),
],
),
]

View file

@ -0,0 +1,24 @@
# Generated by Django 3.2.7 on 2021-11-28 22:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('CollectorAPI', '0008_honeypotagregateperserver'),
]
operations = [
migrations.CreateModel(
name='HoneypotReportsStorage',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('from_date', models.DateTimeField()),
('to_date', models.DateTimeField()),
('affected_honeys', models.JSONField()),
('countries', models.JSONField()),
('data', models.JSONField()),
],
),
]

View file

@ -0,0 +1,17 @@
# Generated by Django 3.2.7 on 2021-11-28 23:20
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('CollectorAPI', '0009_honeypotreportsstorage'),
]
operations = [
migrations.AlterUniqueTogether(
name='honeypotreportsstorage',
unique_together={('from_date', 'to_date')},
),
]

View file

@ -0,0 +1,23 @@
# Generated by Django 3.2.7 on 2022-03-09 09:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('CollectorAPI', '0010_alter_honeypotreportsstorage_unique_together'),
]
operations = [
migrations.CreateModel(
name='PermissionsTable',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('from_date', models.DateTimeField()),
],
options={
'permissions': (('adv_reports', 'Може да създава подробни доклади'), ('graphs', 'Може да изпозлва Graphs'), ('tables', 'Може да използва tables')),
},
),
]

View file

@ -0,0 +1,127 @@
from django.db import models
import random
import hashlib
# Create your models here.
class HoneyPotServer(models.Model):
name = models.CharField(max_length=32)
description = models.CharField(max_length=65500)
ip = models.CharField(max_length=15, unique=True)
key = models.CharField(max_length=64, unique=True, help_text="Enter 0000 to autogenerate key")
last_input = models.DateTimeField(auto_now=True)
isActive = models.BooleanField(default=True)
def __str__(self):
return f"{self.name}"
def save(self, *args, **kwargs):
if self.key == '0000':
self.key = hashlib.sha256(str(random.getrandbits(256)).encode('utf-8')).hexdigest()
return super().save(*args, **kwargs)
"""
class ServerKeys(models.Model):
server = models.ForeignKey(HoneyPotServer, on_delete=models.CASCADE, unique=True)
key = models.CharField(max_length=64, unique=True, help_text="Enter 0000 to autogenerate key")
isActive = models.BooleanField(default=True)
def __str__(self):
return f"{self.server.name}"
"""
class HoneypotInfo(models.Model):
server_id = models.ForeignKey(HoneyPotServer, on_delete=models.PROTECT)
shard_id = models.CharField(max_length=32)
src_ip = models.CharField(max_length=45)
dst_ip = models.CharField(max_length=45)
ip_rep = models.CharField(max_length=128, null=True)
protocol = models.CharField(max_length=45, null=True)
type = models.CharField(max_length=128)
eventid = models.CharField(max_length=128, null=True)
event_type = models.CharField(max_length=128, null=True)
countryISO = models.CharField(max_length=4, null=True)
city_name = models.CharField(max_length=128, null=True)
region_code = models.CharField(max_length=4, null=True)
region_name = models.CharField(max_length=128, null=True)
continent_code = models.CharField(max_length=2, null=True)
latitude = models.DecimalField(max_digits=9, decimal_places=6, null=True)
longitude = models.DecimalField(max_digits=9, decimal_places=6, null=True)
event_timestamp = models.DateTimeField()
recieved_timestamp = models.DateTimeField(auto_now=True)
class Meta:
unique_together = ("shard_id", "event_timestamp")
indexes = [models.Index(fields=["event_timestamp"])]
class HoneypotRawData(models.Model):
record = models.OneToOneField(HoneypotInfo, on_delete=models.PROTECT, to_field='id')
raw_entry = models.JSONField()
class HoneypotAgregate24hIps(models.Model):
ip = models.CharField(max_length=45)
country_iso = models.CharField(max_length=4, null=True)
count = models.IntegerField()
class HoneypotAgregate7DIps(models.Model):
ip = models.CharField(max_length=45)
country_iso = models.CharField(max_length=4, null=True)
count = models.IntegerField()
class HoneypotAgregate30DIps(models.Model):
ip = models.CharField(max_length=45)
country_iso = models.CharField(max_length=4, null=True)
count = models.IntegerField()
class HoneypotAgregate24hCountry(models.Model):
country_iso = models.CharField(max_length=4, null=True)
count = models.IntegerField()
class HoneypotAgregate7DCountry(models.Model):
country_iso = models.CharField(max_length=4, null=True)
count = models.IntegerField()
class HoneypotAgregate30DCountry(models.Model):
country_iso = models.CharField(max_length=4, null=True)
count = models.IntegerField()
class HoneypotAgregatePerServer(models.Model):
data_id = models.CharField(max_length=45)
data = models.JSONField()
class HenoypotInfoHourlyStatisticsAttackIPs(models.Model):
timestamp = models.DateTimeField()
src_ip = models.CharField(max_length=45)
count = models.IntegerField()
class HoneypotReportsStorage(models.Model):
from_date = models.DateTimeField()
to_date = models.DateTimeField()
affected_honeys = models.JSONField()
countries = models.JSONField()
data = models.JSONField()
class Meta:
unique_together = ("from_date", "to_date")
class PermissionsTable(models.Model):
from_date = models.DateTimeField()
class Meta:
permissions = (("adv_reports", "Може да създава подробни доклади"), ("graphs", "Може да изпозлва Graphs"), ("tables", "Може да използва tables"),)

View file

@ -0,0 +1,50 @@
from datetime import datetime, timedelta, date
from time import gmtime, strftime, mktime
import json
import pytz
from django.db import connection
import CollectorAPI.models
import CollectorAPI.models as CollectorDB
from django.db.models import Count
def gen_report(from_date, to_date, affected_honeys, countries, with_data=0):
from_date = datetime.combine(from_date, datetime.min.time())
from_date = from_date.replace(minute=0, hour=0, second=0, microsecond= 0)
date_from = pytz.utc.localize(from_date)
to_date = datetime.combine(to_date, datetime.min.time())
to_date = to_date.replace(hour=23, minute=59, second=59, microsecond=99999)
date_to = pytz.utc.localize(to_date)
attack = CollectorAPI.models.HoneypotInfo.objects.values('src_ip').annotate(icount=Count('src_ip')).\
order_by('icount').filter(event_timestamp__gte=date_from, event_timestamp__lte=date_to,
server_id_id__in=affected_honeys)
if 'all' not in countries:
attack = attack.filter(countryISO__in=countries)
attackers = []
for attacker in attack:
print(attacker['src_ip'])
if with_data == 1:
attackers.append([attacker['src_ip'], attacker['icount']])
else:
attackers.append(attacker['src_ip'])
report = CollectorAPI.models.HoneypotReportsStorage.objects.filter(from_date=date_from, to_date=date_to).exists()
if report:
report = CollectorAPI.models.HoneypotReportsStorage.objects.filter(from_date=date_from, to_date=date_to)
report = report[0]
else:
#let's save to db
report = CollectorAPI.models.HoneypotReportsStorage()
report.from_date = date_from
report.to_date = date_to
report.affected_honeys = json.dumps(affected_honeys)
report.countries = json.dumps(countries)
report.data = json.dumps(attackers)
report.save()
return report.data

View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View file

@ -0,0 +1,29 @@
from django.urls import path
from . import views
urlpatterns = [
path('post', views.post, name='post'),
path('post_local', views.post_local, name='post_local'),
path('targets', views.get_targets, name='get_targets'),
path('time/from', views.get_from_time, name='get_from_time'),
path('time/to', views.get_to_time, name='get_to_time'),
path('report/ips', views.get_attack_ips_json, name='get_attack_ips_json'),
path('report/countries', views.get_attack_countries_json, name='get_attack_ips_json'),
path('report/protocols', views.get_protocols_json, name='get_attack_ips_json'),
path('report/type_per_server', views.get_type_per_server_json, name='get_type_per_server_json'),
# path('sync', views.sync_missing_info, name='sync_missing_info'),
path('agregate/day/ip', views.agregate_ip_24h, name='agregate_ip_day'),
path('agregate/week/ip', views.agregate_ip_7d, name='agregate_ip_week'),
path('agregate/month/ip', views.agregate_ip_30d, name='agregate_ip_month'),
path('agregate/day/country', views.agregate_country_24h, name='agregate_country_day'),
path('agregate/week/country', views.agregate_country_7d, name='agregate_country_week'),
path('agregate/month/country', views.agregate_country_30d, name='agregate_country_month'),
path('agregate/day/perserver', views.agregate_per_server_24h, name='agregate_per_server_day'),
path('agregate/week/perserver', views.agregate_per_server_7d, name='agregate_per_server_week'),
path('agregate/month/perserver', views.agregate_per_server_30d, name='agregate_per_server_month'),
path('agregate/day/perserver/bg', views.agregate_per_server_bg_24h, name='agregate_per_server_bg_day'),
path('agregate/week/perserver/bg', views.agregate_per_server_bg_7d, name='agregate_per_server_bg_week'),
path('agregate/month/perserver/bg', views.agregate_per_server_bg_30d, name='agregate_per_server_bg_month'),
]

View file

@ -0,0 +1,874 @@
from datetime import datetime, timedelta
import json
import pytz
from django.conf import settings
from django.shortcuts import render, redirect
import sys
from django.views.decorators.csrf import csrf_exempt
import CollectorAPI.models as CollectorDB
from django.db import connection
# Create your views here.
from django.http import HttpResponse
# We need to disable CSRF as we are not using it.
@csrf_exempt
def post(request):
"""
URL end point: /API/post
Base post function.
Requires:
POST request
Authorization Header with type "Token xxxxxxx"
File containing the JSON
"""
if request.method != 'POST':
return HttpResponse('Only post method accepted')
if 'Authorization' not in request.headers:
return HttpResponse('Authorization token required')
token = request.headers['Authorization']
token = token.split()
if token[0] != 'Token':
return HttpResponse('What are you trying to pull?')
server = CollectorDB.HoneyPotServer.objects.filter(key=token[1])
if not server:
return HttpResponse('Invalid key ...')
if not server[0].isActive:
return HttpResponse('Invalid key ...')
if server[0].ip != get_client_ip(request):
return HttpResponse('Invalid key ...')
for key, file in request.FILES.items():
tmp_file = file.file
json_data = ''
try:
json_data = json.loads(tmp_file.read())
except:
print('Error loading JSON file from ', server[0])
if post_read_json_and_store(json_data, server[0]):
server[0].save()
print(server[0].name, file=sys.stderr)
return HttpResponse("200 OK")
@csrf_exempt
def post_local(request):
"""
URL end point: /API/post_local
Base post function.
Requires:
POST request
Authorization Header with type "IP xxxxxxx"
File containing the JSON
"""
if request.method != 'POST':
return HttpResponse('Only post method accepted')
if 'Authorization' not in request.headers:
return HttpResponse('Authorization token required')
token = request.headers['Authorization']
token = token.split()
if token[0] != 'IP':
return HttpResponse('What are you trying to pull?')
server = CollectorDB.HoneyPotServer.objects.filter(ip__exact=token[1])
if not server:
return HttpResponse('Invalid key ...')
if not server[0].isActive:
return HttpResponse('Invalid key ...')
if get_client_ip(request) != '127.0.0.1':
return HttpResponse('Invalid key ...')
for key, file in request.FILES.items():
tmp_file = file.file
json_data = ''
try:
json_data = json.loads(tmp_file.read())
except:
print('Error loading JSON file from ', server[0])
if post_read_json_and_store(json_data, server[0]):
server[0].save()
print(server[0].name, file=sys.stderr)
return HttpResponse("200 OK")
def get_targets(request):
if get_client_ip(request) != '127.0.0.1':
return HttpResponse('Invalid key ...')
servers = CollectorDB.HoneyPotServer.objects.filter(isActive=True)
response = []
for server in servers:
response.append(server.ip)
response = ', '.join(response)
get_from_time(request)
return HttpResponse(json.dumps(response), content_type="application/json")
def get_from_time(request):
if get_client_ip(request) != '127.0.0.1':
return HttpResponse('Invalid key ...')
now = datetime.now()
if now.minute < 15:
from_time = now.replace(minute=45, second=0, microsecond=0) - timedelta(hours=1)
elif now.minute < 30:
from_time = now.replace(minute=0, second=0, microsecond=0)
elif now.minute < 45:
from_time = now.replace(minute=15, second=0, microsecond=0)
else:
from_time = now.replace(minute=30, second=0, microsecond=0)
from_time = from_time.strftime("%Y-%m-%dT%H:%M:%S")
return HttpResponse(from_time)
def get_to_time(request):
if get_client_ip(request) != '127.0.0.1':
return HttpResponse('Invalid key ...')
now = datetime.now()
if now.minute < 15:
from_to = now.replace(minute=59, second=59, microsecond=999999) - timedelta(hours=1)
elif now.minute < 30:
from_to = now.replace(minute=14, second=59, microsecond=999999)
elif now.minute < 45:
from_to = now.replace(minute=29, second=59, microsecond=999999)
else:
from_to = now.replace(minute=44, second=59, microsecond=999999)
from_to = from_to.strftime("%Y-%m-%dT%H:%M:%S")
return HttpResponse(from_to)
def get_attack_ips_json(request):
"""
Returns the URL /API/report/ips
Request accepts:
days = int
limit = int
ISO = str
no_count -> If set returns only IPs
:param request:
:return:
"""
if not request.user.is_authenticated:
return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
days = 1
limit = 10
ISO = 'any'
no_count = True
show_iso = False
if 'no_count' in request.GET:
no_count = False
if 'days' in request.GET:
days = int(request.GET['days'])
if 'limit' in request.GET:
limit = int(request.GET['limit'])
if 'iso' in request.GET:
ISO = request.GET['iso']
ISO = ISO.upper()
if 'show_iso' in request.GET:
show_iso = True
if ISO == 'any' and days in [1, 7, 30]:
attacks = gen_agregated_ips(days, limit, no_count, show_iso)
else:
attacks = gen_attack_ips(days, limit, ISO, no_count, show_iso)
return HttpResponse(json.dumps(attacks), content_type="application/json")
def get_attack_countries_json(request):
if not request.user.is_authenticated:
return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
days = 1
limit = 10
no_count = True
if 'no_count' in request.GET:
no_count = False
if 'days' in request.GET:
days = int(request.GET['days'])
if 'limit' in request.GET:
limit = int(request.GET['limit'])
if days in [1, 7, 30]:
attacks = gen_agregated_countries(days, limit, no_count)
else:
attacks = gen_attack_countries(days, limit, no_count)
return HttpResponse(json.dumps(attacks), content_type="application/json")
def get_protocols_json(request):
if not request.user.is_authenticated:
return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
days = 1
limit = 10
no_count = True
if 'no_count' in request.GET:
no_count = False
if 'days' in request.GET:
days = int(request.GET['days'])
if 'limit' in request.GET:
limit = int(request.GET['limit'])
attacks = gen_protocols(days, limit, no_count)
return HttpResponse(json.dumps(attacks), content_type="application/json")
def get_type_per_server_json(request):
if not request.user.is_authenticated:
return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
days = 0
if 'days' in request.GET:
days = int(request.GET['days'])
iso = 'any'
if 'iso' in request.GET:
iso = request.GET['iso']
if days in [1, 7, 30] and iso in ['any', 'bg', 'BG']:
types_per_server = gen_agregated_per_server(days, iso)
else:
types_per_server = json.dumps(gen_type_per_server(days, iso), sort_keys=True)
return HttpResponse(json.dumps(types_per_server), content_type="application/json")
def agregate_per_server_24h(request):
load_data = gen_type_per_server(1)
try:
data = CollectorDB.HoneypotAgregatePerServer.objects.get(data_id='24h_all')
except CollectorDB.HoneypotAgregatePerServer.DoesNotExist:
data = CollectorDB.HoneypotAgregatePerServer()
data.data_id = '24h_all'
data.data = load_data
data.save()
output = ['OK']
return HttpResponse(json.dumps(output), content_type="application/json")
def agregate_per_server_7d(request):
load_data = gen_type_per_server(7)
try:
data = CollectorDB.HoneypotAgregatePerServer.objects.get(data_id='7d_all')
except CollectorDB.HoneypotAgregatePerServer.DoesNotExist:
data = CollectorDB.HoneypotAgregatePerServer()
data.data_id = '7d_all'
data.data = load_data
data.save()
output = ['OK']
return HttpResponse(json.dumps(output), content_type="application/json")
def agregate_per_server_30d(request):
load_data = gen_type_per_server(30)
try:
data = CollectorDB.HoneypotAgregatePerServer.objects.get(data_id='30d_all')
except CollectorDB.HoneypotAgregatePerServer.DoesNotExist:
data = CollectorDB.HoneypotAgregatePerServer()
data.data_id = '30d_all'
data.data = load_data
data.save()
output = ['OK']
return HttpResponse(json.dumps(output), content_type="application/json")
def agregate_per_server_bg_24h(request):
load_data = gen_type_per_server(1, 'bg')
try:
data = CollectorDB.HoneypotAgregatePerServer.objects.get(data_id='24h_bg')
except CollectorDB.HoneypotAgregatePerServer.DoesNotExist:
data = CollectorDB.HoneypotAgregatePerServer()
data.data_id = '24h_bg'
data.data = load_data
data.save()
output = ['OK']
return HttpResponse(json.dumps(output), content_type="application/json")
def agregate_per_server_bg_7d(request):
load_data = gen_type_per_server(7, 'bg')
try:
data = CollectorDB.HoneypotAgregatePerServer.objects.get(data_id='7d_bg')
except CollectorDB.HoneypotAgregatePerServer.DoesNotExist:
data = CollectorDB.HoneypotAgregatePerServer()
data.data_id = '7d_bg'
data.data = load_data
data.save()
output = ['OK']
return HttpResponse(json.dumps(output), content_type="application/json")
def agregate_per_server_bg_30d(request):
load_data = gen_type_per_server(30, 'bg')
try:
data = CollectorDB.HoneypotAgregatePerServer.objects.get(data_id='30d_bg')
except CollectorDB.HoneypotAgregatePerServer.DoesNotExist:
data = CollectorDB.HoneypotAgregatePerServer()
data.data_id = '30d_bg'
data.data = load_data
data.save()
output = ['OK']
return HttpResponse(json.dumps(output), content_type="application/json")
def agregate_ip_24h(request):
load_data = gen_attack_ips(1, 0, 'any', True, True)
CollectorDB.HoneypotAgregate24hIps.objects.all().delete()
query = 'ALTER SEQUENCE \"CollectorAPI_honeypotagregate24hips_id_seq\" RESTART WITH 1;'
with connection.cursor() as cursor:
cursor.execute(query)
for data in load_data:
entry = CollectorDB.HoneypotAgregate24hIps()
entry.ip = data[0]
entry.country_iso = data[1]
entry.count = data[2]
entry.save()
output = ['OK']
return HttpResponse(json.dumps(output), content_type="application/json")
def agregate_ip_bg_24h(request):
load_data = gen_attack_ips(1, 0, 'BG', True, True)
CollectorDB.HoneypotAgregate24hBGIps.objects.all().delete()
query = 'ALTER SEQUENCE \"CollectorAPI_honeypotagregate24hips_id_seq\" RESTART WITH 1;'
with connection.cursor() as cursor:
cursor.execute(query)
for data in load_data:
entry = CollectorDB.HoneypotAgregate24hBGIps()
entry.ip = data[0]
entry.country_iso = data[1]
entry.count = data[2]
entry.save()
output = ['OK']
return HttpResponse(json.dumps(output), content_type="application/json")
def agregate_ip_7d(request):
load_data = gen_attack_ips(7, 0, 'any', True, True)
CollectorDB.HoneypotAgregate7DIps.objects.all().delete()
query = 'ALTER SEQUENCE \"CollectorAPI_honeypotagregate7dips_id_seq\" RESTART WITH 1;'
with connection.cursor() as cursor:
cursor.execute(query)
for data in load_data:
entry = CollectorDB.HoneypotAgregate7DIps()
entry.ip = data[0]
entry.country_iso = data[1]
entry.count = data[2]
entry.save()
output = ['OK']
return HttpResponse(json.dumps(output), content_type="application/json")
def agregate_ip_bg_7d(request):
load_data = gen_attack_ips(7, 0, 'BG', True, True)
CollectorDB.HoneypotAgregate7DIBGps.objects.all().delete()
query = 'ALTER SEQUENCE \"CollectorAPI_honeypotagregate7dibgps_id_seq\" RESTART WITH 1;'
with connection.cursor() as cursor:
cursor.execute(query)
for data in load_data:
entry = CollectorDB.HoneypotAgregate7DIBGps()
entry.ip = data[0]
entry.country_iso = data[1]
entry.count = data[2]
entry.save()
output = ['OK']
return HttpResponse(json.dumps(output), content_type="application/json")
def agregate_ip_30d(request):
load_data = gen_attack_ips(30, 0, 'any', True, True)
CollectorDB.HoneypotAgregate30DIps.objects.all().delete()
query = 'ALTER SEQUENCE \"CollectorAPI_honeypotagregate30dips_id_seq\" RESTART WITH 1;'
with connection.cursor() as cursor:
cursor.execute(query)
for data in load_data:
entry = CollectorDB.HoneypotAgregate30DIps()
entry.ip = data[0]
entry.country_iso = data[1]
entry.count = data[2]
entry.save()
output = ['OK']
return HttpResponse(json.dumps(output), content_type="application/json")
def agregate_ip_bg_30d(request):
load_data = gen_attack_ips(30, 0, 'BG', True, True)
CollectorDB.HoneypotAgregate30DBGIps.objects.all().delete()
query = 'ALTER SEQUENCE \"CollectorAPI_honeypotagregate30dbgips_id_seq\" RESTART WITH 1;'
with connection.cursor() as cursor:
cursor.execute(query)
for data in load_data:
entry = CollectorDB.HoneypotAgregate30DBGIps()
entry.ip = data[0]
entry.country_iso = data[1]
entry.count = data[2]
entry.save()
output = ['OK']
return HttpResponse(json.dumps(output), content_type="application/json")
def agregate_country_24h(request):
load_data = gen_attack_countries(1, 0)
CollectorDB.HoneypotAgregate24hCountry.objects.all().delete()
query = 'ALTER SEQUENCE \"CollectorAPI_honeypotagregate24hcountry_id_seq\" RESTART WITH 1;'
with connection.cursor() as cursor:
cursor.execute(query)
for data in load_data:
entry = CollectorDB.HoneypotAgregate24hCountry()
entry.country_iso = data[0]
entry.count = data[1]
entry.save()
output = ['OK']
return HttpResponse(json.dumps(output), content_type="application/json")
def agregate_country_7d(request):
load_data = gen_attack_countries(7, 0)
CollectorDB.HoneypotAgregate7DCountry.objects.all().delete()
query = 'ALTER SEQUENCE \"CollectorAPI_honeypotagregate7dcountry_id_seq\" RESTART WITH 1;'
with connection.cursor() as cursor:
cursor.execute(query)
for data in load_data:
entry = CollectorDB.HoneypotAgregate7DCountry()
entry.country_iso = data[0]
entry.count = data[1]
entry.save()
output = ['OK']
return HttpResponse(json.dumps(output), content_type="application/json")
def agregate_country_30d(request):
load_data = gen_attack_countries(30, 0)
CollectorDB.HoneypotAgregate30DCountry.objects.all().delete()
query = 'ALTER SEQUENCE \"CollectorAPI_honeypotagregate30dcountry_id_seq\" RESTART WITH 1;'
with connection.cursor() as cursor:
cursor.execute(query)
for data in load_data:
entry = CollectorDB.HoneypotAgregate30DCountry()
entry.country_iso = data[0]
entry.count = data[1]
entry.save()
output = ['OK']
return HttpResponse(json.dumps(output), content_type="application/json")
def sync_missing_info(request):
missing = CollectorDB.HoneypotInfo.objects.filter(countryISO__isnull=False, longitude__isnull=True).order_by('id')\
.all()[:50000]
for p in missing:
hit = p.raw_entry
if 'geoip' in hit['_source']:
if 'city_name' in hit['_source']['geoip']:
p.city_name = hit['_source']['geoip']['city_name']
if 'region_code' in hit['_source']['geoip']:
p.region_code = hit['_source']['geoip']['region_code']
if 'region_name' in hit['_source']['geoip']:
p.region_name = hit['_source']['geoip']['region_name']
if 'continent_code' in hit['_source']['geoip']:
p.continent_code = hit['_source']['geoip']['continent_code']
if 'latitude' in hit['_source']['geoip']:
p.latitude = hit['_source']['geoip']['latitude']
if 'longitude' in hit['_source']['geoip']:
p.longitude = hit['_source']['geoip']['longitude']
p.save()
output = ['OK']
return HttpResponse(json.dumps(output), content_type="application/json")
def post_read_json_and_store(json_data, server_id):
"""
Helper function that should read JSON line by line and set it up in DB
:param json_data:
:param server_id:
:return boolean:
"""
if 'hits' not in json_data:
return False
skipped = 0
for hit in json_data['hits']['hits']:
# If SRC ip is the recieving server - ignore it.
if hit['_source']['src_ip'] == "83.228.89.253":
skipped = skipped + 1
continue
if hit['_source']['src_ip'] == "78.83.66.168":
skipped = skipped + 1
continue
if hit['_source']['src_ip'] == "8.8.8.8":
skipped = skipped + 1
continue
if hit['_source']['src_ip'] == hit['_source']['t-pot_ip_int']:
skipped = skipped + 1
continue
db_hit = CollectorDB.HoneypotInfo()
db_hit.server_id = server_id
db_hit.shard_id = hit['_id']
db_hit.src_ip = hit['_source']['src_ip']
if 'ip_rep' in hit['_source']:
db_hit.ip_rep = hit['_source']['ip_rep']
try:
db_hit.dst_ip = hit['_source']['t-pot_ip_ext']
except:
print('There was error with Honey pot IP for the following record record')
print(hit['_source'])
print('Skipping')
continue
db_hit.type = hit['_source']['type']
if 'protocol' in hit['_source']:
db_hit.protocol = hit['_source']['protocol']
if 'eventid' in hit['_source']:
db_hit.eventid = hit['_source']['eventid']
if 'event_type' in hit['_source']:
db_hit.event_type = hit['_source']['event_type']
if 'geoip' in hit['_source']:
# print(hit['_source']['geoip'])
if 'country_code2' in hit['_source']['geoip']:
# print(hit['_source']['geoip']['country_code2'])
db_hit.countryISO = hit['_source']['geoip']['country_code2']
if 'city_name' in hit['_source']['geoip']:
# print(hit['_source']['geoip']['city_name'])
db_hit.city_name = hit['_source']['geoip']['city_name']
if 'region_code' in hit['_source']['geoip']:
# print(hit['_source']['geoip']['region_code'])
db_hit.region_code = hit['_source']['geoip']['region_code']
if 'region_name' in hit['_source']['geoip']:
# print(hit['_source']['geoip']['region_name'])
db_hit.region_name = hit['_source']['geoip']['region_name']
if 'continent_code' in hit['_source']['geoip']:
db_hit.continent_code = hit['_source']['geoip']['continent_code']
if 'latitude' in hit['_source']['geoip']:
# print(hit['_source']['geoip']['latitude'])
db_hit.latitude = hit['_source']['geoip']['latitude']
if 'longitude' in hit['_source']['geoip']:
# print(hit['_source']['geoip']['longitude'])
db_hit.longitude = hit['_source']['geoip']['longitude']
hit['_source']['timestamp'] = hit['_source']['timestamp'].replace('+0000', 'Z')
if hit['_source']['timestamp'][19] != '.':
datetimeOBJ = datetime.strptime(hit['_source']['timestamp'], '%Y-%m-%dT%H:%M:%SZ')
elif hit['_source']['timestamp'][-1] == 'Z':
datetimeOBJ = datetime.strptime(hit['_source']['timestamp'], '%Y-%m-%dT%H:%M:%S.%fZ')
else:
datetimeOBJ = datetime.strptime(hit['_source']['timestamp'], '%Y-%m-%dT%H:%M:%S.%f')
datetimeOBJ = pytz.utc.localize(datetimeOBJ)
db_hit.event_timestamp = datetimeOBJ
db_hit.recieved_timestamp = datetime.now()
db_hit.raw_entry = hit
try:
db_hit.save()
raw_data = CollectorDB.HoneypotRawData()
raw_data.record_id = db_hit.id
raw_data.raw_entry = hit
try:
raw_data.save()
except:
print('Issue with raw data at ' + hit['_source']['eventid'])
except:
print('Duplicate entry found, Skipping')
print('Entries skipped ' + str(skipped))
return True
def rolling_update(self):
hour_from = datetime.now() - timedelta(hours=1)
hour_from = hour_from.replace(minute=0, second=0, microsecond=0)
hour_to = hour_from.replace(minute=59, second=59, microsecond=999999)
hour_from = pytz.utc.localize(hour_from)
hour_to = pytz.utc.localize(hour_to)
print(hour_from)
def get_client_ip(request):
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip
def gen_agregated_ips(time_limit=1, count_limit=0, show_count=True, show_iso=False):
params = []
query_attack = 'SELECT DISTINCT ip'
if show_iso:
query_attack = query_attack + ', \"country_iso\"'
if show_count:
query_attack = query_attack + ', count'
query_attack = query_attack + ' FROM'
if time_limit == 1:
query_attack = query_attack + ' \"CollectorAPI_honeypotagregate24hips\"'
elif time_limit == 7:
query_attack = query_attack + ' \"CollectorAPI_honeypotagregate7dips\"'
elif time_limit == 30:
query_attack = query_attack + ' \"CollectorAPI_honeypotagregate30dips\"'
else:
raise Exception('Invalid Range')
if show_count:
query_attack = query_attack + ' ORDER BY count DESC'
if count_limit > 0:
query_attack = query_attack + ' LIMIT %s'
params.append(count_limit)
with connection.cursor() as cursor:
cursor.execute(query_attack, params)
attack = cursor.fetchall()
print(connection.queries)
print("debug")
print(attack)
return attack
def gen_attack_ips(time_limit=0, count_limit=0, iso='any', show_count=True, show_iso=False, show_city=False):
"""
:param time_limit: -> time in days
:param count_limit: -> How many entries to get
:param iso: -> country ISO
:param show_count: -> Should it get count
:return: Array
"""
params = []
query_attack = 'SELECT DISTINCT src_ip'
if show_iso:
query_attack = query_attack + ', \"countryISO\"'
if show_city:
query_attack = query_attack + ', city_name'
if show_count:
query_attack = query_attack + ', COUNT(src_ip) as count'
query_attack = query_attack + ' FROM \"CollectorAPI_honeypotinfo\" '
if time_limit > 0 or iso != 'any':
query_attack = query_attack + ' WHERE'
if time_limit > 0:
date_from = datetime.now() - timedelta(days=time_limit)
date_from = pytz.utc.localize(date_from)
params.append(date_from)
query_attack = query_attack + ' event_timestamp > %s'
if iso != 'any' and iso.isalnum():
if time_limit > 0:
query_attack = query_attack + ' AND'
query_attack = query_attack + ' \"countryISO\" LIKE %s'
params.append(iso.upper())
if show_count:
query_attack = query_attack + ' GROUP BY src_ip'
if show_iso:
query_attack = query_attack + ', \"countryISO\"'
if show_city:
query_attack = query_attack + ', city_name'
if show_count:
query_attack = query_attack + ' ORDER BY count DESC'
if count_limit > 0:
query_attack = query_attack + ' LIMIT %s'
params.append(count_limit)
with connection.cursor() as cursor:
cursor.execute(query_attack, params)
attack = cursor.fetchall()
return attack
def gen_agregated_countries(time_limit=0, count_limit=0, show_count=True):
params = []
query_countries = 'SELECT DISTINCT \"country_iso\"'
if show_count:
query_countries = query_countries + ', count'
if time_limit == 1:
query_countries = query_countries + ' FROM \"CollectorAPI_honeypotagregate24hcountry\"'
elif time_limit == 7:
query_countries = query_countries + ' FROM \"CollectorAPI_honeypotagregate7dcountry\"'
elif time_limit == 30:
query_countries = query_countries + ' FROM \"CollectorAPI_honeypotagregate30dcountry\"'
else:
raise Exception('Invalid Range')
if show_count:
query_countries = query_countries + ' ORDER BY count DESC'
if count_limit > 0:
query_countries = query_countries + ' LIMIT %s'
params.append(count_limit)
with connection.cursor() as cursor:
cursor.execute(query_countries, params)
countries = cursor.fetchall()
return countries
def gen_attack_countries(time_limit=0, count_limit=0, show_count=True):
"""
Function used to generate array of countries and attacks
:param time_limit:
:param count_limit:
:param show_count:
:return:
"""
params = []
query_countries = 'SELECT DISTINCT \"countryISO\"'
if show_count:
query_countries = query_countries + ', COUNT(\"countryISO\") as count'
query_countries = query_countries + ' FROM \"CollectorAPI_honeypotinfo\"'
if time_limit > 0:
date_from = datetime.now() - timedelta(days=time_limit)
date_from = pytz.utc.localize(date_from)
params.append(date_from)
query_countries = query_countries + ' WHERE event_timestamp > %s'
if show_count:
query_countries = query_countries + ' GROUP BY \"countryISO\" ORDER BY count DESC'
if count_limit > 0:
query_countries = query_countries + ' LIMIT %s'
params.append(count_limit)
with connection.cursor() as cursor:
cursor.execute(query_countries, params)
countries = cursor.fetchall()
return countries
def gen_protocols(time_limit=0, count_limit=0, show_count=True):
"""
Generate protocol statistics
:param time_limit:
:param count_limit:
:param show_count:
:return:
"""
params = []
query_protocols = 'SELECT DISTINCT protocol'
if show_count:
query_protocols = query_protocols + ', COUNT(protocol) as count'
query_protocols = query_protocols + ' FROM \"CollectorAPI_honeypotinfo\"'
if time_limit > 0:
date_from = datetime.now() - timedelta(days=time_limit)
date_from = pytz.utc.localize(date_from)
params.append(date_from)
query_protocols = query_protocols + ' WHERE event_timestamp > %s'
if show_count:
query_protocols = query_protocols + ' GROUP BY \"protocol\" ORDER BY count DESC'
if count_limit > 0:
query_protocols = query_protocols + ' LIMIT %s'
params.append(count_limit)
with connection.cursor() as cursor:
cursor.execute(query_protocols, params)
protocols = cursor.fetchall()
return protocols
def gen_agregated_per_server(time_limit=0, iso='any'):
"""
Get already generated reports for attack types per server from DB and pass it as JSON
:param time_limit:
:param iso:
:return:
"""
if time_limit == 1 and iso == 'any':
data = CollectorDB.HoneypotAgregatePerServer.objects.get(data_id="24h_all")
elif time_limit == 7 and iso == 'any':
data = CollectorDB.HoneypotAgregatePerServer.objects.get(data_id="7d_all")
elif time_limit == 30 and iso == 'any':
data = CollectorDB.HoneypotAgregatePerServer.objects.get(data_id="30d_all")
elif time_limit == 1 and iso in ['bg', 'BG']:
data = CollectorDB.HoneypotAgregatePerServer.objects.get(data_id="24h_bg")
elif time_limit == 7 and iso in ['bg', 'BG']:
data = CollectorDB.HoneypotAgregatePerServer.objects.get(data_id="7d_bg")
elif time_limit == 30 and iso in ['bg', 'BG']:
data = CollectorDB.HoneypotAgregatePerServer.objects.get(data_id="30d_bg")
else:
raise Exception('Invalid Range')
output = json.dumps(data.data, sort_keys=True)
return output
def gen_type_per_server(time_limit=0, iso='any'):
"""
Generate attacks per server report and save it in the DB
:param time_limit:
:param iso:
:return:
"""
params = []
query_type_per_server = 'SELECT DISTINCT i.type, i.server_id_id, s.name, s.ip, COUNT(i.type) as count'
query_type_per_server = query_type_per_server + ' FROM "CollectorAPI_honeypotinfo" as i LEFT JOIN'
query_type_per_server = query_type_per_server + ' "CollectorAPI_honeypotserver" as s ON (s.id = i.server_id_id)'
if time_limit > 0 or iso != 'any':
query_type_per_server = query_type_per_server + ' WHERE'
if time_limit > 0:
date_from = datetime.now() - timedelta(days=time_limit)
date_from = pytz.utc.localize(date_from)
params.append(date_from)
query_type_per_server = query_type_per_server + ' event_timestamp > %s'
if iso != 'any' and iso.isalnum():
if time_limit > 0:
query_type_per_server = query_type_per_server + ' AND'
query_type_per_server = query_type_per_server + ' \"countryISO\" = %s'
params.append(iso.upper())
query_type_per_server = query_type_per_server + ' GROUP BY i.type, i.server_id_id, s.name, s.ip'
query_type_per_server = query_type_per_server + ' ORDER BY i.server_id_id, i.type;'
with connection.cursor() as cursor:
cursor.execute(query_type_per_server, params)
type_per_server = cursor.fetchall()
output = {}
tmp_storage = {}
tmp_storage['01_comult'] = 0
for tps in type_per_server:
tmp_storage[tps[0]] = 0
for tps in type_per_server:
output[tps[1]] = {'name': tps[2], 'ip': tps[3], 'data': tmp_storage.copy()}
for tps in type_per_server:
if output[tps[1]]['data'][tps[0]] == 0:
output[tps[1]]['data']['01_comult'] += tps[4]
output[tps[1]]['data'][tps[0]] = tps[4]
return output

View file

View file

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View file

@ -0,0 +1,6 @@
from django.apps import AppConfig
class CollectorwebConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'CollectorWEB'

View file

@ -0,0 +1,26 @@
from django import forms
import datetime
import CollectorAPI.models
class GenerateAdvReport(forms.Form):
honey_request = CollectorAPI.models.HoneyPotServer.objects.all().values_list('id', 'name').order_by('id')
honeys = []
for honey in honey_request:
honeys.append([honey[0], honey[1]])
countries = [
['all', 'all'],
['BG', 'България'],
['RU', 'Русия']
]
from_date = forms.DateField(widget=forms.SelectDateWidget(years=range(2021, datetime.date.today().year+10)),
label='От дата')
to_date = forms.DateField(widget=forms.SelectDateWidget(years=range(2021, datetime.date.today().year+10)),
label='До дата',
initial=datetime.datetime.now())
affected_honeys = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, choices=honeys,
label="Засегнати Хъни потове")
attacker_countries = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, choices=countries,
label="Държава на атакуващият")

View file

View file

@ -0,0 +1,122 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{% block title %}{% endblock %}</title>
{% load static %}
<link rel="stylesheet" href="{% static 'bootstrap/icons/bootstrap-icons.css' %}">
<link href="{% static 'bootstrap/css/bootstrap.min.css' %}" rel="stylesheet" integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous">
<script src="{% static 'js/lib/jquery-3.6.0.min.js' %}" ></script>
<script src="{% static 'js/lib/jquery-ui-1.13.0-rc.2/jquery-ui.min.js' %}" ></script>
<script src="{% static 'js/lib/echarts.min.js' %}" ></script>
<script src="{% static 'js/lib/mathjax/tex-chtml.js' %}"></script>
<script src="{% static 'js/popper.min.js' %}"></script>
<script src="{% static 'bootstrap/js/bootstrap.js' %}"></script>
<script src="{% static 'bootstrap/js/bootstrap.bundle.js' %}"></script>
<script src="{% static 'js/code/main.js' %}" ></script>
<link rel='stylesheet' href="{% static 'css/default.css' %}">
<script>
$(document).ready( function() {
$('.dropdown-toggle').dropdown();
});
</script>
</head>
<body class="d-flex flex-column min-vh-100">
<header>
<!-- Image and text -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="navbar-brand" href="#"><img src="{% static 'images/logo.png' %}" class="logo" width="35px" style="margin-right: 8px;">HoneyPot Admin</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
{% if user.is_authenticated %}
<li class="nav-item active">
{% url 'index' as the_url %}
<a class="nav-link" href="{{ the_url }}" target="_blank">Overview</a>
</li>
<li class="nav-item">
{% url 'reports' as the_url %}
<a class="nav-link" href="{{ the_url }}" id="navbarReports" target="_blank">
Общи доклади
</a>
</li>
{% if perms.CollectorAPI.adv_reports %}
<li class="nav-item">
{% url 'reports_adv' as the_url %}
<a class="nav-link" href="{{ the_url }}" id="navbarReports" target="_blank">
Подробни доклади
</a>
</li>
{% endif %}
{% if perms.CollectorAPI.graphs %}
<li class="nav-item">
{% url 'graphs' as the_url %}
<a class="nav-link" href="{{ the_url }}" id="navbarReports" target="_blank">
Graphs
</a>
</li>
{% endif %}
{% if perms.CollectorAPI.tables %}
<li class="nav-item">
{% url 'tables' as the_url %}
<a class="nav-link" href="{{ the_url }}" id="navbarReports" target="_blank">
Tables
</a>
</li>
{% endif %}
<li class="nav-item">
<a class="nav-link" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">{{ user.username }}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'logout' %}">(Излез</a>
</li>
<li>
<a class="nav-link" href="{% url 'password_change' %}">Смени паролата)</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{% url 'login' %}">Log In</a>
</li>
{% endif %}
</ul>
</div>
</nav>
</header>
<main>
{% block content %}
{% endblock %}
</main>
<!-- Footer -->
<footer class="bg-dark text-center text-white mt-auto">
<!-- Grid container -->
<div class="container p-4">
<!-- Section: Text -->
<section class="mb-4">
<p>Статистическа система за анализ на информацията от HoneyPot порталите, поръчна и разработена за <a href="https://e-gov.bg" target="_blank">Държавна агенция "Електронно управление"</a></p>
<p>Внимание - Системата е само за вътрешно ползване!!!</p>
</section>
<!-- Section: Text -->
</div>
<!-- Grid container -->
<!-- Copyright -->
<!-- <div class="text-center p-3" style="background-color: rgba(0, 0, 0, 0.2);">
© 2021 Copyright:
<a class="text-white" href="https://anavaro.com/">Stanislav Atanasov</a> & <a class="text-white" href="https://www.linkedin.com/in/teodor-boyadzhiev-39463195/">Teodor Boyadzhiev</a>
</div> -->
<!-- Copyright -->
</footer>
<!-- Footer -->
<script>
{% block javascript %}
{% endblock %}
</script>
</body>
</html>

View file

@ -0,0 +1,18 @@
{% extends 'base.html' %}
{% block title %}Графики{% endblock %}
{% block content %}
<table class="center" id="main">
<tr> <td> {% include "registration/_global_filter.html" %}</td></tr>
<tr id="attack-sources"> <td> {% include "registration/_attack_sources.html" %} </td> </tr>
<tr id="attack-trend"> <td> {% include "registration/_attack_trend.html" %} </td> </tr>
<tr id="multiple-targets"> <td> {% include "registration/_multiple_targets.html" %}</td></tr>
<tr id="multiple-targets-summary"> <td> {% include "registration/_multiple_targets_summary.html" %}</td></tr>
</table>
{% endblock %}
{% block javascript %}
{% endblock %}

View file

@ -0,0 +1,358 @@
{% extends 'base.html' %}
{% block title %}Home{% endblock %}
{% load static %}
{% block content %}
<div class="container">
<div class="row justify-content-md-center">
<div class="col-md-4">
<nav>
<div class="nav nav-tabs" id="nav-tab" role="tablist">
<button class="nav-link active" id="nav-ips-24h-tab" data-bs-toggle="tab" data-bs-target="#nav-ips-24h" type="button" role="tab" aria-controls="nav-home1" aria-selected="true">24H</button>
<button class="nav-link" id="nav-ips-7d-tab" data-bs-toggle="tab" data-bs-target="#nav-ips-7d" type="button" role="tab" aria-controls="nav-profile" aria-selected="false">7 Days</button>
<button class="nav-link" id="nav-ips-30d-tab" data-bs-toggle="tab" data-bs-target="#nav-ips-30d" type="button" role="tab" aria-controls="nav-contact" aria-selected="false">30 Days</button>
</div>
</nav>
<div class="tab-content" id="nav-tabContent">
<div class="tab-pane fade show active" id="nav-ips-24h" role="tabpanel" aria-labelledby="nav-home-tab">
<table id="table-24h" data-toggle="table" class="table">
<thead>
<tr>
<th colspan="2">Top 10 атакуващи IP-та (24H):</th>
</tr>
<tr>
<th scope="col">IP</th>
<th scope="col"></th>
<th scope="col">Count</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div class="d-flex justify-content-center table-24h-spinner">
<div class="spinner-border table-24h-spinner" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
</div>
<div class="tab-pane fade" id="nav-ips-7d" role="tabpanel" aria-labelledby="nav-profile-tab">
<table id="table-7D" data-toggle="table" data-ajax="ajaxRequest7D" class="table">
<thead>
<tr>
<th colspan="2">Top 10 атакуващи IP-та (7D):</th>
</tr>
<tr>
<th scope="col">IP</th>
<th scope="col"></th>
<th scope="col">Count</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div class="d-flex justify-content-center table-7D-spinner">
<div class="spinner-border table-7D-spinner" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
</div>
<div class="tab-pane fade" id="nav-ips-30d" role="tabpanel" aria-labelledby="nav-contact-tab">
<table id="table-30D" data-toggle="table" data-ajax="ajaxRequest30D" class="table">
<thead>
<tr>
<th colspan="2">Top 10 атакуващи IP-та (30D):</th>
</tr>
<tr>
<th scope="col">IP</th>
<th scope="col"></th>
<th scope="col">Count</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div class="d-flex justify-content-center table-30D-spinner">
<div class="spinner-border table-30D-spinner" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<table class="table" id="table-proto-24h">
<thead>
<tr>
<th colspan="2">Top 10 attacked protocols (24H):</th>
</tr>
<tr>
<th scope="col">Protocol</th>
<th scope="col">Count</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div class="d-flex justify-content-center table-proto-24h-spinner">
<div class="spinner-border table-proto-24h-spinner" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
</div>
<div class="col-md-4">
<div class="nav nav-tabs" id="nav-tab" role="tablist">
<button class="nav-link active" id="nav-countries-24h-tab" data-bs-toggle="tab" data-bs-target="#nav-countries-24h" type="button" role="tab" aria-controls="nav-home1" aria-selected="true">24H</button>
<button class="nav-link" id="nav-countries-7D-tab" data-bs-toggle="tab" data-bs-target="#nav-countries-7D" type="button" role="tab" aria-controls="nav-home2" aria-selected="true">7D</button>
<button class="nav-link" id="nav-countries-30D-tab" data-bs-toggle="tab" data-bs-target="#nav-countries-30D" type="button" role="tab" aria-controls="nav-home3" aria-selected="true">30D</button>
</div>
<div class="tab-content" id="nav-tabContent">
<div class="tab-pane fade show active" id="nav-countries-24h" role="tabpanel" aria-labelledby="nav-home-tab">
<table class="table" id="table-countries-24h">
<thead>
<tr>
<th colspan="2">Top 10 атакуващи страни (24H):</th>
</tr>
<tr>
<th scope="col">Country</th>
<th scope="col">Count</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div class="d-flex justify-content-center table-countries-24h-spinner">
<div class="spinner-border table-countries-24h-spinner" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
</div>
<div class="tab-pane fade" id="nav-countries-7D" role="tabpanel" aria-labelledby="nav-home-tab">
<table class="table" id="table-countries-7D">
<thead>
<tr>
<th colspan="2">Top 10 атакуващи страни (7D):</th>
</tr>
<tr>
<th scope="col">Country</th>
<th scope="col">Count</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div class="d-flex justify-content-center table-countries-7D-spinner">
<div class="spinner-border table-countries-7D-spinner" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
</div>
<div class="tab-pane fade" id="nav-countries-30D" role="tabpanel" aria-labelledby="nav-home-tab">
<table class="table" id="table-countries-30D">
<thead>
<tr>
<th colspan="2">Top 10 атакуващи страни (30D):</th>
</tr>
<tr>
<th scope="col">Country</th>
<th scope="col">Count</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div class="d-flex justify-content-center table-countries-30D-spinner">
<div class="spinner-border table-countries-30D-spinner" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row justify-content-md-center">
<div class="col-md-12">
<div class="nav nav-tabs" id="nav-tab" role="tablist">
<button class="nav-link active" id="nav-types-24h-bg-tab" data-bs-toggle="tab" data-bs-target="#nav-types-24h-bg" type="button" role="tab" aria-controls="nav-home1" aria-selected="true">24H</button>
<button class="nav-link" id="nav-types-7D-bg-tab" data-bs-toggle="tab" data-bs-target="#nav-types-7D-bg" type="button" role="tab" aria-controls="nav-home2" aria-selected="true">7D</button>
<button class="nav-link" id="nav-types-30D-bg-tab" data-bs-toggle="tab" data-bs-target="#nav-types-30D-bg" type="button" role="tab" aria-controls="nav-home3" aria-selected="true">30D</button>
</div>
<div class="tab-content" id="nav-tabContent">
<div class="tab-pane fade show active" id="nav-types-24h-bg" role="tabpanel" aria-labelledby="nav-home-tab">
<table class="table table-type-per-server-24H-bg" id="table-type-per-server-24H-bg">
<thead>
<tr>
<th>Per Server (BULGARIA)</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
{% url 'report_per_server_csv' as the_url %}
<a href="{{ the_url }}?filter=24h_bg">CSV</a>
<div class="d-flex justify-content-center table-type-per-server-24H-bg-spinner">
<div class="spinner-border table-type-per-server-24H-bg-spinner" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
</div>
<div class="tab-pane fade" id="nav-types-7D-bg" role="tabpanel" aria-labelledby="nav-home-tab">
<table class="table table-type-per-server-7D-bg" id="table-type-per-server-7D-bg">
<thead>
<tr>
<th>Per Server (BULGARIA)</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
{% url 'report_per_server_csv' as the_url %}
<a href="{{ the_url }}?filter=7d_bg">CSV</a>
<div class="d-flex justify-content-center table-type-per-server-7D-bg-spinner">
<div class="spinner-border table-type-per-server-7D-bg-spinner" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
</div>
<div class="tab-pane fade" id="nav-types-30D-bg" role="tabpanel" aria-labelledby="nav-home-tab">
<table class="table table-type-per-server-30D-bg" id="table-type-per-server-30D-bg">
<thead>
<tr>
<th>Per Server (BULGARIA)</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
{% url 'report_per_server_csv' as the_url %}
<a href="{{ the_url }}?filter=30d_bg">CSV</a>
<div class="d-flex justify-content-center table-type-per-server-30D-bg-spinner">
<div class="spinner-border table-type-per-server-30D-bg-spinner" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row justify-content-md-center">
<div class="col-md-12">
<div class="nav nav-tabs" id="nav-tab" role="tablist">
<button class="nav-link active" id="nav-types-24h-tab" data-bs-toggle="tab" data-bs-target="#nav-types-24h" type="button" role="tab" aria-controls="nav-home1" aria-selected="true">24H</button>
<button class="nav-link" id="nav-types-7D-tab" data-bs-toggle="tab" data-bs-target="#nav-types-7D" type="button" role="tab" aria-controls="nav-home2" aria-selected="true">7D</button>
<button class="nav-link" id="nav-types-30D-tab" data-bs-toggle="tab" data-bs-target="#nav-types-30D" type="button" role="tab" aria-controls="nav-home3" aria-selected="true">30D</button>
</div>
<div class="tab-content" id="nav-tabContent">
<div class="tab-pane fade show active" id="nav-types-24h" role="tabpanel" aria-labelledby="nav-home-tab">
<table class="table table-type-per-server-24H" id="table-type-per-server-24H">
<thead>
<tr>
<th>Per Server (International)</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
{% url 'report_per_server_csv' as the_url %}
<a href="{{ the_url }}?filter=24h_all">CSV</a>
<div class="d-flex justify-content-center table-type-per-server-24H-spinner">
<div class="spinner-border table-type-per-server-24H-spinner" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
</div>
<div class="tab-pane fade" id="nav-types-7D" role="tabpanel" aria-labelledby="nav-home-tab">
<table class="table table-type-per-server-7D" id="table-type-per-server-7D">
<thead>
<tr>
<th>Per Server</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
{% url 'report_per_server_csv' as the_url %}
<a href="{{ the_url }}?filter=7d_all">CSV</a>
<div class="d-flex justify-content-center table-type-per-server-7D-spinner">
<div class="spinner-border table-countries-30D-spinner" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
</div>
<div class="tab-pane fade" id="nav-types-30D" role="tabpanel" aria-labelledby="nav-home-tab">
<table class="table table-type-per-server-30D" id="table-type-per-server-30D">
<thead>
<tr>
<th>Per Server</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
{% url 'report_per_server_csv' as the_url %}
<a href="{{ the_url }}?filter=30d_all">CSV</a>
<div class="d-flex justify-content-center table-type-per-server-30D-spinner">
<div class="spinner-border table-countries-30D-spinner" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row justify-content-md-center">
<div class="col-md-12">
{% load humanize %}
<table class="table">
<thead>
<tr>
<th colspan="5">Статус на сървъра</th>
</tr>
<tr>
<th scope="col">Име на сървъра</th>
<th scope="col">Описание на сървъра</th>
<th scope="col">IP на сървъра</th>
<th scope="col">Последно получена информация</th>
<th scope="col">Активен ли е?</th>
<th></th>
</tr>
</thead>
<tbody>
{% if servers %}
{% for server in servers %}
<tr>
<td>{{ server.1 }}</td>
<td>{{ server.2 }}</td>
<td>{{ server.3 }}</td>
<td>{{ server.6|naturaltime }}</td>
<td>{% if server.4 %}<i class="bi bi-check-circle-fill" style="color: #0f5132"></i>{% else %}<i class="bi bi-dash-circle-fill" style="color: #a52834"></i>{% endif %}</td>
<td><a href="https://{{ server.3 }}:64297" target="_blank"><i class="bi bi-terminal"></i></a></td>
</tr>
{% endfor %}
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
<script src="{% static 'js/home.js' %}"></script>
{% endblock %}
{% block javascript %}
{% endblock %}

View file

@ -0,0 +1,94 @@
<div id="attack-src-title"
class="stat-sec-title center">
<table class="fullwidth">
<tr>
<td class="fullwidth">
Attack Sources
</td>
<td class="section-controls">
<span id="attack-src-hide" class="button"
onclick="toggle('attack-src-body')"> Toggle </span>
</td>
</tr>
</table>
</div>
<div id="attack-src-body" class="stat-body">
<div id="attack-src-filters" class="stat-sec-filters center">
<table>
<tr>
<td class="filter-fields">
<table>
<tr>
<td>
<label for="attack-src-filters-startD"
class="filter-label">Start Date: </label>
</td>
<td>
<input
class="datepicker"
id="attack-src-filters-startD"
onchange="setIfLess('attack-src-filters-startD', 'attack-src-filters-endD')">
</td>
</tr>
<tr>
<td>
<label for="attack-src-filters-endD"
class="filter-label"> End Date: </label>
</td>
<td>
<input
class="datepicker"
id="attack-src-filters-endD"
onchange="setIfGreater('attack-src-filters-endD', 'attack-src-filters-startD')">
</td>
</tr>
<tr>
<td>
<label for="attack-src-filters-minattacks"
class="filter-label"> Minimum Attacks: </label>
</td>
<td>
<input
class="numberpicker"
id="attack-src-filters-minattacks"
onchange="verifyPositiveNumber('attack-src-filters-minattacks')"
type="number">
</td>
</tr>
<tr>
<td>
<label class="filter-label" for="attack-src-filters-bgonly"> Bulgarian IPs Only: </label>
</td>
<td>
<input id="attack-src-filters-bg_only" type="checkbox">
</td>
</tr>
</table>
</td>
<td>
<table class="filter-buttons">
<tr>
<td>
<span id="attack-src-filters-clear" class="button"
onclick="clearFilters(['attack-src-filters-startD',
'attack-src-filters-endD',
'attack-src-filters-minattacks'])"> Clear </span>
</td>
</tr>
<tr>
<td>
<span id="attack-src-filters-apply" class="button"
onclick="updateAttackGraph()"> Apply </span>
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
<div id="attack-src-canvas" class="ga-charts center canvas" onclick="resizeEChart($('#attack-src-canvas'))"></div>
</div>

View file

@ -0,0 +1,122 @@
<div id="attack-trend-title"
class="stat-sec-title center">
<table class="fullwidth">
<tr>
<td class="fullwidth">
Attack Trend
</td>
<td class="section-controls">
<span id="attack-trend-hide" class="button"
onclick="toggle('attack-trend-body')"> Toggle </span>
</td>
</tr>
</table>
</div>
<div id="attack-trend-body" class="stat-body">
<div id="attack-trend-filters" class="stat-sec-filters center">
<table>
<tr>
<td class="filter-fields">
<table>
<tr>
<td>
<label for="attack-trend-filters-startD"
class="filter-label">Start Date: </label>
</td>
<td>
<input
class="datepicker"
id="attack-trend-filters-startD"
onchange="setIfLess('attack-trend-filters-startD', 'attack-trend-filters-endD')">
</td>
</tr>
<tr>
<td>
<label for="attack-trend-filters-endD"
class="filter-label"> End Date: </label>
</td>
<td>
<input
class="datepicker"
id="attack-trend-filters-endD"
onchange="setIfGreater('attack-trend-filters-endD', 'attack-trend-filters-startD')">
</td>
</tr>
<tr>
<td>
<label for="attack-trend-filters-resolution"
class="filter-label"> Resolution: </label>
</td>
<td>
<select class="select" id="attack-trend-filters-resolution">
<option value="hour"> Hour </option>
<option value="day" selected> Day </option>
<option value="week"> Week </option>
</select>
</td></tr>
<tr>
<td> <label
for="attack-trend-filters-bg_only"
class="filter-label"> Bulgarian IPs Only: </label> </td>
<td>
<input id="attack-trend-filters-bg_only" type="checkbox">
</td>
</tr>
</table>
</td>
<td>
<table class="filter-buttons">
<tr>
<td>
<span id="attack-trend-filters-clear" class="button"
onclick="clearFilters(['attack-trend-filters-startD',
'attack-trend-filters-endD'])"> Clear </span>
</td>
</tr>
<tr>
<td>
<span id="attack-trend-filters-apply" class="button"
onclick="updateAttackTrend()"> Apply </span>
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
<div id="attack-trend-canvas" class="ga-charts center canvas" onclick="resizeEChart($('#attack-trend-canvas'))"></div>
<div id="attack-trend-info-panel" class="info-panel"> </div>
<table id="attack-trend-parameters" class="parameter-table">
<tr><td class="column-title">Parameter</td><td class="column-title">Value</td></tr>
<tr><td>a</td><td id="attack-trend-param-value-a"></td></tr>
<tr><td>b</td><td id="attack-trend-param-value-b"></td></tr>
<tr><td>\(\sigma_a\)</td><td id="attack-trend-param-value-sigma_a"></td></tr>
</table>
<div class="details">
<details>
<summary>Details</summary>
<p> All numbers in the results are rounded to 2 digits after the decimal point. </p>
<p>
For modelling the trend of the attacks is used a linear model,
$$ y = a t + b + \epsilon$$
$$ \epsilon \sim N(0, \mu)$$
where y is the number of attacks, t is the time step, a is scale, and b is offset.
The time-step is determined by the resolution, which can be hour, day or week.
</p>
<p>
The parameters of the model are fit according to
$$ a = \frac{\sum_i (y - \mu_y)(t_i - \mu_t)}{\sum_i(t_i - \mu_t)^2} $$
$$ b = \mu_y - a \mu_t $$
$$ \mu_t = \frac{1}{n} \sum_{i=1}^n t_i $$
$$ \mu_y = \frac{1}{n} \sum_{i=1}^n y_i $$
Then the uncertainty of the scale is estimated by assuming Gaussian distribution of the errors, according to
$$ \sigma_a = \sqrt{\frac{\sum_i (y_i - a x_i - b)^2}{(n-2)\sum_i (x_i - \mu_x)^2}} $$
</p>
<p> For further details, please check <a href="https://en.wikipedia.org/wiki/Simple_linear_regression"> linear regression</a>. </p>
</details>
</div>
</div>

View file

@ -0,0 +1,14 @@
<table>
<tr>
<td> <label for="global-filters-ip"
class="filter-label"> IP: </label> </td>
<td> <input id="global-filters-ip"> </td>
</tr>
<tr>
<td colspan="2">
<span id="attack-trend-filters-clear" class="button"
onclick="clearFilters(['global-filters-ip'])"> Clear </span>
</td>
</tr>
</table>

View file

@ -0,0 +1,29 @@
<div id="multiple-targets-title"
class="stat-sec-title center">
<table class="fullwidth">
<tr>
<td class="fullwidth">
Multiple Targets
</td>
<td class="section-controls">
<span id="attack-multi-hide" class="button"
onclick="toggle('attack-multi-body')"> Toggle </span>
</td>
</tr>
</table>
</div>
<div id="attack-multi-body" class="stat-body" >
<div id="attack-multi-filters" class="stat-sec-filters center">
<table class="filter-buttons">
<tr>
<td>
<span id="attack-multi-filters-apply" class="button"
onclick="updateMulti()"> Apply </span>
</td>
</tr>
</table>
</div>
<div id="attack-multi-canvas" class="ga-charts center canvas" onclick="resizeEChart($('#attack-multi-canvas'))"></div>
</div>

View file

@ -0,0 +1,29 @@
<div id="multiple-targets-summary-title"
class="stat-sec-title center">
<table class="fullwidth">
<tr>
<td class="fullwidth">
Multiple Targets Summary
</td>
<td class="section-controls">
<span id="attack-multi-summary-hide" class="button"
onclick="toggle('attack-multi-summary-body')"> Toggle </span>
</td>
</tr>
</table>
</div>
<div id="attack-multi-summary-body" class="stat-body" >
<div id="attack-multi-summary-filters" class="stat-sec-filters center">
<table class="filter-buttons">
<tr>
<td>
<span id="attack-multi-summary-filters-apply" class="button"
onclick="updateMultiSummary()"> Apply </span>
</td>
</tr>
</table>
</div>
<div id="attack-multi-summary-canvas" class="ga-charts center canvas" onclick="resizeEChart($('#attack-multi-summary-canvas'))"></div>
</div>

View file

@ -0,0 +1,12 @@
{% extends 'base.html' %}
{% block title %}Login{% endblock %}
{% block content %}
<h2>Log In</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Log In</button>
</form>
{% endblock %}

View file

@ -0,0 +1,194 @@
{% extends 'base.html' %}
{% block title %}Доклади{% endblock %}
{% load static %}
{% block content %}
<div class="container">
<div class="row justify-content-md-center">
<div class="col-md-12">
<H1>Създаване на репорти</H1>
<p>Можете да използвате предоставените връзки за да генерирате различни репорти. За удобство може да избирате между CSV и JSON.</p>
</div>
</div>
<div class="row justify-content-md-center">
<div class="col-md-12">
<H1>България</H1>
<p>Репорти на регионални атаки.</p>
<table class="table">
<thead>
<tr>
<th colspan="8">CSV</th>
</tr>
<tr>
<th colspan="2">24 Часа</th>
<th colspan="2">7 дни</th>
<th colspan="2">30 дни</th>
<th colspan="2">365 дни</th>
</tr>
</thead>
<tbody>
<tr>
{% url 'report_ips_csv' as reports_ips_csv %}
<td><a href="{{ reports_ips_csv }}?days=1&iso=BG"> Със статистика</a></td>
<td><a href="{{ reports_ips_csv }}?days=1&iso=BG&no_count"> Без статистика</a></td>
<td><a href="{{ reports_ips_csv }}?days=7&iso=BG"> Със статистика</a></td>
<td><a href="{{ reports_ips_csv }}?days=7&iso=BG&no_count"> Без статистика</a></td>
<td><a href="{{ reports_ips_csv }}?days=30&iso=BG"> Със статистика</a></td>
<td><a href="{{ reports_ips_csv }}?days=30&iso=BG&no_count"> Без статистика</a></td>
<td><a href="{{ reports_ips_csv }}?days=365&iso=BG"> Със статистика</a></td>
<td><a href="{{ reports_ips_csv }}?days=365&iso=BG&no_count"> Без статистика</a></td>
</tr>
</tbody>
</table>
<table class="table">
<thead>
<tr>
<th colspan="8">JSON</th>
</tr>
<tr>
<th colspan="2">24 Часа</th>
<th colspan="2">7 дни</th>
<th colspan="2">30 дни</th>
<th colspan="2">365 дни</th>
</tr>
</thead>
<tbody>
<tr>
{% url 'report_ips_json' as report_ips_json %}
<td><a href="{{ report_ips_json }}?days=1&iso=BG"> Със статистика</a></td>
<td><a href="{{ report_ips_json }}?days=1&iso=BG&no_count"> Без статистика</a></td>
<td><a href="{{ report_ips_json }}?days=7&iso=BG"> Със статистика</a></td>
<td><a href="{{ report_ips_json }}?days=7&iso=BG&no_count"> Без статистика</a></td>
<td><a href="{{ report_ips_json }}?days=30&iso=BG"> Със статистика</a></td>
<td><a href="{{ report_ips_json }}?days=30&iso=BG&no_count"> Без статистика</a></td>
<td><a href="{{ report_ips_json }}?days=365&iso=BG"> Със статистика</a></td>
<td><a href="{{ report_ips_json }}?days=365&iso=BG&no_count"> Без статистика</a></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row justify-content-md-center">
<div class="col-md-12">
<H1>Русия</H1>
<p>Репорти на регионални атаки.</p>
<table class="table">
<thead>
<tr>
<th colspan="8">CSV</th>
</tr>
<tr>
<th colspan="2">24 Часа</th>
<th colspan="2">7 дни</th>
<th colspan="2">30 дни</th>
<th colspan="2">365 дни</th>
</tr>
</thead>
<tbody>
<tr>
{% url 'report_ips_csv' as reports_ips_csv %}
<td><a href="{{ reports_ips_csv }}?days=1&iso=RU"> Със статистика</a></td>
<td><a href="{{ reports_ips_csv }}?days=1&iso=RU&no_count"> Без статистика</a></td>
<td><a href="{{ reports_ips_csv }}?days=7&iso=RU"> Със статистика</a></td>
<td><a href="{{ reports_ips_csv }}?days=7&iso=RU&no_count"> Без статистика</a></td>
<td><a href="{{ reports_ips_csv }}?days=30&iso=RU"> Със статистика</a></td>
<td><a href="{{ reports_ips_csv }}?days=30&iso=RU&no_count"> Без статистика</a></td>
<td><a href="{{ reports_ips_csv }}?days=365&iso=RU"> Със статистика</a></td>
<td><a href="{{ reports_ips_csv }}?days=365&iso=RU&no_count"> Без статистика</a></td>
</tr>
</tbody>
</table>
<table class="table">
<thead>
<tr>
<th colspan="8">JSON</th>
</tr>
<tr>
<th colspan="2">24 Часа</th>
<th colspan="2">7 дни</th>
<th colspan="2">30 дни</th>
<th colspan="2">365 дни</th>
</tr>
</thead>
<tbody>
<tr>
{% url 'report_ips_json' as report_ips_json %}
<td><a href="{{ report_ips_json }}?days=1&iso=RU"> Със статистика</a></td>
<td><a href="{{ report_ips_json }}?days=1&iso=RU&no_count"> Без статистика</a></td>
<td><a href="{{ report_ips_json }}?days=7&iso=RU"> Със статистика</a></td>
<td><a href="{{ report_ips_json }}?days=7&iso=RU&no_count"> Без статистика</a></td>
<td><a href="{{ report_ips_json }}?days=30&iso=RU"> Със статистика</a></td>
<td><a href="{{ report_ips_json }}?days=30&iso=RU&no_count"> Без статистика</a></td>
<td><a href="{{ report_ips_json }}?days=365&iso=RU"> Със статистика</a></td>
<td><a href="{{ report_ips_json }}?days=365&iso=RU&no_count"> Без статистика</a></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row justify-content-md-center">
<div class="col-md-12">
<H1>Света</H1>
<p>Репорти на международни атаки.</p>
<p class="alert-warning">Ако желаете да генерирате доклади за определна държава - използвайте специфичен URL или се обърнете към програмиста</p>
<table class="table">
<thead>
<tr>
<th colspan="8">CSV</th>
</tr>
<tr>
<th colspan="2">24 Часа</th>
<th colspan="2">7 дни</th>
<th colspan="2">30 дни</th>
<th colspan="2">365 дни</th>
</tr>
</thead>
<tbody>
<tr>
{% url 'report_ips_csv' as reports_ips_csv %}
<td><a href="{{ reports_ips_csv }}?days=1"> Със статистика</a></td>
<td><a href="{{ reports_ips_csv }}?days=1&no_count"> Без статистика</a></td>
<td><a href="{{ reports_ips_csv }}?days=7"> Със статистика</a></td>
<td><a href="{{ reports_ips_csv }}?days=7&no_count"> Без статистика</a></td>
<td><a href="{{ reports_ips_csv }}?days=30"> Със статистика</a></td>
<td><a href="{{ reports_ips_csv }}?days=30&no_count"> Без статистика</a></td>
<td><a href="{{ reports_ips_csv }}?days=365"> Със статистика</a></td>
<td><a href="{{ reports_ips_csv }}?days=365&no_count"> Без статистика</a></td>
</tr>
</tbody>
</table>
<table class="table">
<thead>
<tr>
<th colspan="8">JSON</th>
</tr>
<tr>
<th colspan="2">24 Часа</th>
<th colspan="2">7 дни</th>
<th colspan="2">30 дни</th>
<th colspan="2">365 дни</th>
</tr>
</thead>
<tbody>
<tr>
{% url 'report_ips_json' as report_ips_json %}
<td><a href="{{ report_ips_json }}?days=1"> Със статистика</a></td>
<td><a href="{{ report_ips_json }}?days=1&no_count"> Без статистика</a></td>
<td><a href="{{ report_ips_json }}?days=7"> Със статистика</a></td>
<td><a href="{{ report_ips_json }}?days=7&no_count"> Без статистика</a></td>
<td><a href="{{ report_ips_json }}?days=30"> Със статистика</a></td>
<td><a href="{{ report_ips_json }}?days=30&no_count"> Без статистика</a></td>
<td><a href="{{ report_ips_json }}?days=365"> Със статистика</a></td>
<td><a href="{{ report_ips_json }}?days=365&no_count"> Без статистика</a></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}
{% block javascript %}
{% endblock %}

View file

@ -0,0 +1,66 @@
{% extends 'base.html' %}
{% block title %}Създаване на доклади{% endblock %}
{% load static %}
{% block content %}
<div class="container">
<div class="row justify-content-md-center">
<div class="col-md-12">
<H1>Създаване на репорти</H1>
<p>Можете да използвате предоставените връзки за да генерирате различни репорти. За удобство може да избирате между CSV и JSON.</p>
</div>
</div>
<div class="row justify-content-md-center">
<div class="col-md-12">
<form action="/reports_adv" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit">
</form>
</div>
</div>
<div class="row justify-content-md-center">
<div class="col-md-12 center">
<h1>Генерирани доклади</h1>
<table class="table table-type-per-server-24H" id="table-type-per-server-24H">
<thead>
<tr>
<th>Начална дата</th>
<th>Крайна дата</th>
<th>Хъни потове</th>
<th>Държава</th>
<th></th>
</tr>
</thead>
<tbody>
{% for report in reports %}
<tr>
<td>{{ report.from_date }}</td>
<td>{{ report.to_date }}</td>
<td>
{% for honey in report.affected_honeys %}
{{ honey }} <br/>
{% endfor %}
</td>
<td>
{% for country in report.countries %}
{{ country }}
{% endfor %}
</td>
<td>
{% url 'reports_adv' as the_url %}
<a href="{{ the_url }}?r={{ report.id }}">CSV</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}
{% block javascript %}
{% endblock %}

View file

@ -0,0 +1,51 @@
{% block title %}Дневна таблица{% endblock %}
{% if has_data %}
<table id="table-daily-value">
<tr>
{% for target, data in table_data.items %}
<td class="tables-target">
<table class="tables-rows">
<tr class="column-title">
<td colspan="5" class="tables-target-title"> {{ target }}</td>
</tr>
<tr>
<td rowspan="2"> Дата</td>
<td rowspan="2"> Всички Атаки</td>
<td colspan="3"> България</td>
</tr>
<tr>
<td> Атаки</td>
<td> IP</td>
<td> Брой</td>
</tr>
<tr>
<td class="summary">
{{ date }}
</td>
<td class="summary">
{{ data.all_attacks }}
</td>
<td class="summary">
{{ data.bg_attacks }}
</td>
<td>
{{ data.top_ip }}
</td>
<td>
{{ data.top_count }}
</td>
</tr>
{% for count, ip in data.rest_top_ips %}
<tr>
<td> </td> <td> </td> <td> </td> <td> {{ ip }} </td> <td> {{ count }} </td>
</tr>
{% endfor %}
</table>
</td>
{% endfor %}
</tr>
</table>
{% else %}
<div> Няма данни за {{ date }}</div>
{% endif %}

View file

@ -0,0 +1,52 @@
{% block title %}Детайли{% endblock %}
<table class="tables-rows" id="table-details-value">
<tr class="column-title">
<td> Дата </td>
{% for target in targets %}
<td colspan="3"> {{ target }} </td>
{% endfor %}
<td colspan="3"> Общо </td>
</tr>
<tr>
<td></td>
{% for target in targets %}
<td> Всички </td>
<td> България </td>
<td> България IPs </td>
{% endfor %}
<td> Всички </td>
<td> България </td>
<td> България IPs </td>
</tr>
{% for time_point in time_points %}
<tr>
<td> {{ time_point }} </td>
{% for target in targets %}
{% if time_point in data and target in data|keyvalue:time_point %}
<td> {{ data|keyvalue:time_point|keyvalue:target|keyvalue:"all"|keyvalue:"attacks" }} </td>
<td> {{ data|keyvalue:time_point|keyvalue:target|keyvalue:"bg"|keyvalue:"attacks" }} </td>
<td> {{ data|keyvalue:time_point|keyvalue:target|keyvalue:"bg"|keyvalue:"ips" }} </td>
{% else %}
<td> 0 </td>
<td> 0 </td>
<td> 0 </td>
{% endif %}
{% endfor %}
<td class="summary"> {{ total_over_targets|keyvalue:time_point|keyvalue:"all"|keyvalue:"attacks" }}</td>
<td class="summary"> {{ total_over_targets|keyvalue:time_point|keyvalue:"bg"|keyvalue:"attacks" }}</td>
<td class="summary"> {{ total_over_targets|keyvalue:time_point|keyvalue:"bg"|keyvalue:"ips" }}</td>
</tr>
{% endfor %}
<tr class="summary">
<td></td>
{% for target in targets %}
<td> {{ total_over_time|keyvalue:target|keyvalue:"all"|keyvalue:"attacks" }}</td>
<td> {{ total_over_time|keyvalue:target|keyvalue:"bg"|keyvalue:"attacks" }}</td>
<td> {{ total_over_time|keyvalue:target|keyvalue:"bg"|keyvalue:"ips" }}</td>
{% endfor %}
<td> {{ total|keyvalue:"all"|keyvalue:"attacks" }}</td>
<td> {{ total|keyvalue:"bg"|keyvalue:"attacks" }}</td>
<td> {{ total|keyvalue:"bg"|keyvalue:"ips" }}</td>
</tr>
</table>

View file

@ -0,0 +1,61 @@
{% block title %}Опростен таблица{% endblock %}
<table class="tables-rows" id="table-simple-value">
<tr class="column-title">
<td></td>
<td colspan="2"> {{ date24h.start }} - {{ date24h.end }}</td>
<td colspan="2"> {{ date7d.start }} - {{ date7d.end }}</td>
<td colspan="2"> {{ date30d.start }} - {{ date30d.end }}</td>
</tr>
<tr>
<td></td>
<td colspan="2"> 24 часа </td>
<td colspan="2"> 7 дена </td>
<td colspan="2"> 30 дена </td>
</tr>
<tr>
<td></td>
<td> Всички </td>
<td> България </td>
<td> Всички </td>
<td> България </td>
<td> Всички </td>
<td> България </td>
</tr>
{% for target in all_targets %}
<tr>
<td> {{ target }}</td>
{% if target in summary24h %}
<td> {{ summary24h|keyvalue:target|keyvalue:"total" }}</td>
<td> {{ summary24h|keyvalue:target|keyvalue:"domestic" }} </td>
{% else %}
<td> 0 </td>
<td> 0 </td>
{% endif %}
{% if target in summary7d %}
<td> {{ summary7d|keyvalue:target|keyvalue:"total" }}</td>
<td> {{ summary7d|keyvalue:target|keyvalue:"domestic" }} </td>
{% else %}
<td> 0 </td>
<td> 0 </td>
{% endif %}
{% if target in summary30d %}
<td> {{ summary30d|keyvalue:target|keyvalue:"total" }}</td>
<td> {{ summary30d|keyvalue:target|keyvalue:"domestic" }} </td>
{% else %}
<td> 0 </td>
<td> 0 </td>
{% endif %}
</tr>
{% endfor %}
<tr class="summary">
<td> </td>
<td> {{ total|keyvalue:"summary24h"|keyvalue:"total"}} </td>
<td> {{ total|keyvalue:"summary24h"|keyvalue:"domestic"}} </td>
<td> {{ total|keyvalue:"summary7d"|keyvalue:"total"}} </td>
<td> {{ total|keyvalue:"summary7d"|keyvalue:"domestic"}} </td>
<td> {{ total|keyvalue:"summary30d"|keyvalue:"total"}} </td>
<td> {{ total|keyvalue:"summary30d"|keyvalue:"domestic"}} </td>
</tr>
</table>

View file

@ -0,0 +1,163 @@
{% extends 'base.html' %}
{% block title %}Таблици{% endblock %}
{% block content %}
<div id="tables-daily">
<div id="tables-daily-filters" class="stat-sec-filters center">
<div class="stat-sec-title"> Daily </div>
<table>
<tr>
<td>
<label for="tables-daily-filters-date"
class="filter-label"> Избор на ден: </label>
</td>
<td>
<input
class="datepicker"
id="tables-daily-filters-date">
</td>
</tr>
</table>
<table class="filter-buttons">
<tr>
<td>
<span id="tables-daily-filters-apply" class="button"
onclick="updateTablesDaily()"> Приложи </span>
</td>
<td>
<span id="tables-daily-filters-clear" class="button"
onclick="clearFilters(['tables-daily-filters-date'])"> Изчисти </span>
</td>
</tr>
<tr>
<td><span onclick="exportTableToCSV('table-daily-value', 'daily.csv')" class="button"> Export to CSV </span>
</td>
<td>
<span onclick="exportTableToExcel('table-daily-value', 'daily.xls')" class="button"> Export to Excel </span>
</td>
</tr>
</table>
</div>
<div id="tables-daily-result"></div>
</div>
<div id="tables-simple">
<div id="tables-simple-filters" class="stat-sec-filters center">
<div class="stat-sec-title"> Simple </div>
<table>
<tr>
<td>
<label for="tables-simple-filters-date"
class="filter-label"> Дата: </label>
</td>
<td>
<input
class="datepicker"
id="tables-simple-filters-date">
</td>
</tr>
</table>
<table class="filter-buttons">
<tr>
<td>
<span id="tables-simple-filters-apply" class="button"
onclick="updateTablesSimple()"> Приложи </span>
</td>
<td>
<span id="tables-simple-filters-clear" class="button"
onclick="clearFilters(['tables-simple-filters-date'])"> Изчисти </span>
</td>
</tr>
<tr>
<td><span onclick="exportTableToCSV('table-simple-value', 'simple.csv')" class="button"> Export to CSV </span>
</td>
<td>
<span onclick="exportTableToExcel('table-simple-value', 'simple.xls')" class="button"> Export to Excel </span>
</td>
</tr>
</table>
</div>
<div id="tables-simple-result"></div>
</div>
<div id="tables-details">
<div id="tables-details-filters" class="stat-sec-filters center">
<div class="stat-sec-title"> Details </div>
<table>
<tr>
<td>
<label for="tables-details-filters-startD"
class="filter-label"> Начална Дата: </label>
</td>
<td>
<input
class="datepicker"
id="tables-details-filters-startD"
onchange="setIfLess('tables-details-filters-startD', 'tables-details-filters-endD')">
</td>
</tr>
<tr>
<td>
<label for="tables-details-filters-startD"
class="filter-label"> Крайна Дата: </label>
</td>
<td>
<input
class="datepicker"
id="tables-details-filters-endD"
onchange="setIfGreater('tables-details-filters-endD', 'tables-details-filters-startD')">
</td>
</tr>
<tr>
<td>
<label for="tables-details-filters-resolution"
class="filter-label"> Резолюция: </label>
</td>
<td>
<select class="select" id="tables-details-filters-resolution">
<option value="day"> Ден </option>
<option value="week" selected> Седмица </option>
<option value="month"> Месец </option>
</select>
</td>
</tr>
</table>
<table class="filter-buttons">
<tr>
<td>
<span id="tables-details-filters-apply" class="button"
onclick="updateTablesDetails()"> Приложи </span>
</td>
<td>
<span id="tables-details-filters-clear" class="button"
onclick="clearFilters([
'tables-details-filters-startD',
'tables-details-filters-endD'])"> Изчисти </span>
</td>
</tr>
<tr>
<td><span onclick="exportTableToCSV('table-details-value', 'details.csv')" class="button"> Export to CSV </span>
</td>
<td>
<span onclick="exportTableToExcel('table-details-value', 'details.xls')" class="button"> Export to Excel </span>
</td>
</tr>
</table>
</div>
<div id="tables-details-result"></div>
</div>
{% endblock %}
{% block javascript %}
{% endblock %}

View file

@ -0,0 +1,5 @@
from django.template.defaultfilters import register
@register.filter
def keyvalue(dict, key):
return dict[key]

View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View file

@ -0,0 +1,21 @@
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
path('reports', views.reports, name='reports'),
path('reports_adv', views.reports_adv, name='reports_adv'),
path('reports/ips_csv', views.get_report_ips_csv, name='report_ips_csv'),
path('reports/ips_json', views.get_report_ips_json, name='report_ips_json'),
path('reports/per_server_csv', views.get_report_agregated_per_server_csv, name='report_per_server_csv'),
path('attack_graph/<str:start_date>/<str:end_date>/<int:min_attacks>/<str:global_ip>/<int:bg_only>', views.attack_graph, name='attack_graph'),
path('attack_trend/<str:start_date>/<str:end_date>/<str:resolution>/<str:global_ip>/<int:bg_only>', views.attack_trend, name='attack_trend'),
path('multi_target/<str:ip>', views.multi_target, name='multi_target'),
path('multi_target_summary/<str:ip>', views.multi_target_summary, name='multi_target_summary'),
path('graphs', views.graphs, name='graphs'),
path('tables', views.tables, name='tables'),
path('table_daily/<str:date>', views.table_daily, name='table_daily'),
path('table_simple/<str:date>', views.table_simple, name='table_simple'),
path('table_details/<str:start>/<str:end>/<str:res>', views.table_details, name='table_details')
]

File diff suppressed because it is too large Load diff

View file

View file

@ -0,0 +1,16 @@
"""
ASGI config for HoneyPotCollector project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'HoneyPotCollector.settings')
application = get_asgi_application()

View file

@ -0,0 +1,24 @@
"""HoneyPotCollector URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('', include('CollectorWEB.urls')),
path('API/', include('CollectorAPI.urls')),
path('admin/', admin.site.urls),
path('accounts/', include('django.contrib.auth.urls')), # new
]

View file

@ -0,0 +1,16 @@
"""
WSGI config for HoneyPotCollector project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'HoneyPotCollector.settings')
application = get_wsgi_application()

View file

@ -1 +1,121 @@
### collector
# HoneyPot Data collector
HoneyPot Data collector is software intended to collect information from all TPot CE honey pots, deployed in DAEU.
## Requirements
- Python 3.9
- PostgreSQL (developed on 13.4, but should work on any)
## Installation
1. Clone repo
``
git cone https://github.com/satanasov/HoneyPotCollector.git
``
2. Install requirements
```
cd HonePotCollector
pip install -r requirements.txt
```
3. Config SQL user
4. Make migrations
```
python3 manage.py migrate
```
5. Create super user
```
python manage.py createsuperuser
```
6. Start server as daemon
```
nohup python3 manage.py runserver 0.0.0.0:8000 > ~/collector.log &
```
## Configuration
### SSH Keys
Please create private/public key pair for the user you are going to run the server. Copy public keys to all machines you are going to scrape.
### Add target servers
Go to `http://<serverip>:8000/admin` and login with superuser.
Go to "Collectorapi" > "Honey pot servers" and add servers, with IPs (use `0000` as key to generate random key)
### Automate
Add crontab to run `collector.sh` each 15 minutes (+1 just in case)
```
1,16,31,46 * * * * /bin/bash /home/collector/HoneyPotCollector/collector.sh 1> /home/collector/cron.log
```
# API Documentation
## CollectorAPI
### /API/targets
Get list of all active IPs we have to crawl
**Responds only on localhost requests**
### /API/from_time
Get from time for the current request
**Responds only on localhost requests**
### /API/to_time
Get to time for the current request
**Responds only on localhost requests**
### /API/post_local
Local post instance. Accepts only from localhost and is used to load the JSON files we get with the collector scripts.
**Responds only on localhost requests**
### /API/post
Remote post instance. Accepts only from verified active keys and servers. Loads the remote send JSON files.
### /API/report/ips
Returns JSON formatted list of results related to attacking IPs
Accepts the following GET paramaters:
- **days** (int) - how many hours back should the report return
- **limit** (int)- How many results should it return
- **iso** (str) - for which country should results be returned
- **no_count** - boolen for should the attack count be returned
### /API/report/countries
Returns JSON formatted list of results related to attacking countries
Accepts the following GET paramaters:
- **days** (int) - how many hours back should the report return
- **limit** (int)- How many results should it return
- **no_count** - boolen for should the attack count be returned
### /API/report/protocols
Returns JSON formatted list of results related to attacked protocols
Accepts the following GET paramaters:
- **days** (int) - how many hours back should the report return
- **limit** (int)- How many results should it return
- **no_count** - boolen for should the attack count be returned

29
collector/collector.sh Normal file
View file

@ -0,0 +1,29 @@
#!/bin/bash
# Get times
from=$(curl http://127.0.0.1:8000/API/time/from)
to=$(curl http://127.0.0.1:8000/API/time/to)
# Get all targets
arr=$(curl http://127.0.0.1:8000/API/targets)
# Split targets
targets=$(echo $arr | sed -e 's/\[ //g' -e 's/\ ]//g' -e 's/\,//g')
# For each target DO
for target in ${targets[@]}; do
# Remove quotations
clean_target=${target//\"}
# Execute SSH command to get data.json
ssh -p 64295 tsec@${clean_target} "curl -XGET --header 'Content-Type: application/json' http://localhost:64298/logstash-*/_search?pretty=true -d'{\"size\":10000,\"query\":{\"range\":{\"timestamp\":{\"from\":\"${from}\",\"to\":\"${to}\",\"time_zone\":\"+03:00\"}}}}'" > /tmp/data.json
# Push data to local
curl -XPOST --header "Authorization: IP ${clean_target}" http://127.0.0.1:8000/API/post_local -F 'data=@/tmp/data.json'
rm /tmp/data.json
done
curl http://127.0.0.1:8000/API/agregate/day/ip
curl http://127.0.0.1:8000/API/agregate/day/country
curl http://127.0.0.1:8000/API/agregate/day/perserver
curl http://127.0.0.1:8000/API/agregate/day/perserver/bg

41740
collector/fixtures/honey.sql Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,9 @@
#!/bin/bash
curl http://127.0.0.1:8000/API/agregate/week/ip
curl http://127.0.0.1:8000/API/agregate/week/country
curl http://127.0.0.1:8000/API/agregate/week/perserver
curl http://127.0.0.1:8000/API/agregate/week/perserver/bg
curl http://127.0.0.1:8000/API/agregate/month/ip
curl http://127.0.0.1:8000/API/agregate/month/country
curl http://127.0.0.1:8000/API/agregate/month/perserver
curl http://127.0.0.1:8000/API/agregate/month/perserver/bg

22
collector/manage.py Normal file
View file

@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'HoneyPotCollector.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,7 @@
Django==3.2.7
psycopg2==2.9.1
psycopg2-binary==2.9.1
pytz==2021.1
sqlparse==0.4.2
asgiref==3.4.1
numpy==1.21.2

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,481 @@
/*!
* Bootstrap Reboot v5.1.1 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors
* Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/
:root {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg-rgb: 255, 255, 255;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-bg: #fff;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
background-color: currentColor;
border: 0;
opacity: 0.25;
}
hr:not([size]) {
height: 1px;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title],
abbr[data-bs-original-title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-left: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.2em;
background-color: #fcf8e3;
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: #0d6efd;
text-decoration: underline;
}
a:hover {
color: #0a58ca;
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: var(--bs-font-monospace);
font-size: 1em;
direction: ltr /* rtl:ignore */;
unicode-bidi: bidi-override;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: #d63384;
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.2rem 0.4rem;
font-size: 0.875em;
color: #fff;
background-color: #212529;
border-radius: 0.2rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
font-weight: 700;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: #6c757d;
text-align: left;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]::-webkit-calendar-picker-indicator {
display: none;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: left;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: left;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
outline-offset: -2px;
-webkit-appearance: textfield;
}
/* rtl:raw:
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::file-selector-button {
font: inherit;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,478 @@
/*!
* Bootstrap Reboot v5.1.1 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors
* Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/
:root {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg-rgb: 255, 255, 255;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-bg: #fff;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
background-color: currentColor;
border: 0;
opacity: 0.25;
}
hr:not([size]) {
height: 1px;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title],
abbr[data-bs-original-title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-right: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-right: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.2em;
background-color: #fcf8e3;
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: #0d6efd;
text-decoration: underline;
}
a:hover {
color: #0a58ca;
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: var(--bs-font-monospace);
font-size: 1em;
direction: ltr ;
unicode-bidi: bidi-override;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: #d63384;
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.2rem 0.4rem;
font-size: 0.875em;
color: #fff;
background-color: #212529;
border-radius: 0.2rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
font-weight: 700;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: #6c757d;
text-align: right;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]::-webkit-calendar-picker-indicator {
display: none;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: right;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: right;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
outline-offset: -2px;
-webkit-appearance: textfield;
}
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::file-selector-button {
font: inherit;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-alarm-fill" viewBox="0 0 16 16">
<path d="M6 .5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1H9v1.07a7.001 7.001 0 0 1 3.274 12.474l.601.602a.5.5 0 0 1-.707.708l-.746-.746A6.97 6.97 0 0 1 8 16a6.97 6.97 0 0 1-3.422-.892l-.746.746a.5.5 0 0 1-.707-.708l.602-.602A7.001 7.001 0 0 1 7 2.07V1h-.5A.5.5 0 0 1 6 .5zm2.5 5a.5.5 0 0 0-1 0v3.362l-1.429 2.38a.5.5 0 1 0 .858.515l1.5-2.5A.5.5 0 0 0 8.5 9V5.5zM.86 5.387A2.5 2.5 0 1 1 4.387 1.86 8.035 8.035 0 0 0 .86 5.387zM11.613 1.86a2.5 2.5 0 1 1 3.527 3.527 8.035 8.035 0 0 0-3.527-3.527z"/>
</svg>

After

Width:  |  Height:  |  Size: 626 B

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-alarm" viewBox="0 0 16 16">
<path d="M8.5 5.5a.5.5 0 0 0-1 0v3.362l-1.429 2.38a.5.5 0 1 0 .858.515l1.5-2.5A.5.5 0 0 0 8.5 9V5.5z"/>
<path d="M6.5 0a.5.5 0 0 0 0 1H7v1.07a7.001 7.001 0 0 0-3.273 12.474l-.602.602a.5.5 0 0 0 .707.708l.746-.746A6.97 6.97 0 0 0 8 16a6.97 6.97 0 0 0 3.422-.892l.746.746a.5.5 0 0 0 .707-.708l-.601-.602A7.001 7.001 0 0 0 9 2.07V1h.5a.5.5 0 0 0 0-1h-3zm1.038 3.018a6.093 6.093 0 0 1 .924 0 6 6 0 1 1-.924 0zM0 3.5c0 .753.333 1.429.86 1.887A8.035 8.035 0 0 1 4.387 1.86 2.5 2.5 0 0 0 0 3.5zM13.5 1c-.753 0-1.429.333-1.887.86a8.035 8.035 0 0 1 3.527 3.527A2.5 2.5 0 0 0 13.5 1z"/>
</svg>

After

Width:  |  Height:  |  Size: 711 B

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-bottom" viewBox="0 0 16 16">
<rect width="4" height="12" x="6" y="1" rx="1"/>
<path d="M1.5 14a.5.5 0 0 0 0 1v-1zm13 1a.5.5 0 0 0 0-1v1zm-13 0h13v-1h-13v1z"/>
</svg>

After

Width:  |  Height:  |  Size: 271 B

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-center" viewBox="0 0 16 16">
<path d="M8 1a.5.5 0 0 1 .5.5V6h-1V1.5A.5.5 0 0 1 8 1zm0 14a.5.5 0 0 1-.5-.5V10h1v4.5a.5.5 0 0 1-.5.5zM2 7a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V7z"/>
</svg>

After

Width:  |  Height:  |  Size: 315 B

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-end" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M14.5 1a.5.5 0 0 0-.5.5v13a.5.5 0 0 0 1 0v-13a.5.5 0 0 0-.5-.5z"/>
<path d="M13 7a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V7z"/>
</svg>

After

Width:  |  Height:  |  Size: 318 B

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-middle" viewBox="0 0 16 16">
<path d="M6 13a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1v10zM1 8a.5.5 0 0 0 .5.5H6v-1H1.5A.5.5 0 0 0 1 8zm14 0a.5.5 0 0 1-.5.5H10v-1h4.5a.5.5 0 0 1 .5.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 316 B

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-start" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M1.5 1a.5.5 0 0 1 .5.5v13a.5.5 0 0 1-1 0v-13a.5.5 0 0 1 .5-.5z"/>
<path d="M3 7a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V7z"/>
</svg>

After

Width:  |  Height:  |  Size: 318 B

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-top" viewBox="0 0 16 16">
<rect width="4" height="12" rx="1" transform="matrix(1 0 0 -1 6 15)"/>
<path d="M1.5 2a.5.5 0 0 1 0-1v1zm13-1a.5.5 0 0 1 0 1V1zm-13 0h13v1h-13V1z"/>
</svg>

After

Width:  |  Height:  |  Size: 287 B

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-alt" viewBox="0 0 16 16">
<path d="M1 13.5a.5.5 0 0 0 .5.5h3.797a.5.5 0 0 0 .439-.26L11 3h3.5a.5.5 0 0 0 0-1h-3.797a.5.5 0 0 0-.439.26L5 13H1.5a.5.5 0 0 0-.5.5zm10 0a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 0-1h-3a.5.5 0 0 0-.5.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 326 B

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-app-indicator" viewBox="0 0 16 16">
<path d="M5.5 2A3.5 3.5 0 0 0 2 5.5v5A3.5 3.5 0 0 0 5.5 14h5a3.5 3.5 0 0 0 3.5-3.5V8a.5.5 0 0 1 1 0v2.5a4.5 4.5 0 0 1-4.5 4.5h-5A4.5 4.5 0 0 1 1 10.5v-5A4.5 4.5 0 0 1 5.5 1H8a.5.5 0 0 1 0 1H5.5z"/>
<path d="M16 3a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/>
</svg>

After

Width:  |  Height:  |  Size: 387 B

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-app" viewBox="0 0 16 16">
<path d="M11 2a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3h6zM5 1a4 4 0 0 0-4 4v6a4 4 0 0 0 4 4h6a4 4 0 0 0 4-4V5a4 4 0 0 0-4-4H5z"/>
</svg>

After

Width:  |  Height:  |  Size: 282 B

Some files were not shown because too many files have changed in this diff Show more