GeoIP download settings, work in progress for https://github.com/opnsense/core/issues/3856

This commit is contained in:
Ad Schellevis 2019-12-25 16:00:09 +01:00
parent bb35688f0e
commit 789eac7d99
10 changed files with 158 additions and 60 deletions

View File

@ -39,6 +39,7 @@ class AliasController extends IndexController
{
// include dialog form definitions
$this->view->selected_alias = $selected;
$this->view->formGeoIPSettings = $this->getForm("geoIPSettings");
$this->view->pick('OPNsense/Firewall/alias');
}
}

View File

@ -313,4 +313,26 @@ class AliasController extends ApiMutableModelControllerBase
}
return $result;
}
/**
* get geoip settings (and stats)
*/
public function getGeoIPAction()
{
$result = array();
if ($this->request->isGet()) {
$result[static::$internalModelName] = array();
$node = $this->getModel()->getNodeByReference('geoip');
if ($node != null) {
$result[static::$internalModelName]['geoip'] = $node->getNodes();
}
if (file_exists('/usr/local/share/GeoIP/alias.stats')) {
$stats = json_decode(file_get_contents('/usr/local/share/GeoIP/alias.stats'), true);
$result[static::$internalModelName]['geoip'] = array_merge(
$result[static::$internalModelName]['geoip'], $stats
);
}
}
return $result;
}
}

View File

@ -0,0 +1,20 @@
<form>
<field>
<id>alias.geoip.timestamp</id>
<label>Last updated</label>
<type>info</type>
<help><![CDATA[Last updated timestamp.]]></help>
</field>
<field>
<id>alias.geoip.address_count</id>
<label>Total number of ranges</label>
<type>info</type>
<help><![CDATA[Total number of entries in downloaded set.]]></help>
</field>
<field>
<id>alias.geoip.url</id>
<label>Url</label>
<type>text</type>
<help><![CDATA[Location to fetch geoip address ranges from.]]></help>
</field>
</form>

View File

@ -5,6 +5,10 @@
Firewall aliases
</description>
<items>
<geoip>
<url type="UrlField">
</url>
</geoip>
<aliases>
<alias type="ArrayField">
<enabled type="BooleanField">

View File

@ -330,22 +330,46 @@
});
});
let data_get_map = {'frm_GeopIPSettings':"/api/firewall/alias/getGeoIP"};
mapDataToFormUI(data_get_map).done(function(data){
formatTokenizersUI();
$('.selectpicker').selectpicker('refresh');
});
/**
* reconfigure
*/
$("#reconfigureAct").click(function(){
$("#reconfigureAct_progress").addClass("fa fa-spinner fa-pulse");
ajaxCall("/api/firewall/alias/reconfigure", {}, function(data,status) {
// when done, disable progress animation.
$("#reconfigureAct_progress").removeClass("fa fa-spinner fa-pulse");
saveFormToEndpoint("/api/firewall/alias/set", 'frm_GeopIPSettings', function(){
ajaxCall("/api/firewall/alias/reconfigure", {}, function(data,status) {
// when done, disable progress animation.
$("#reconfigureAct_progress").removeClass("fa fa-spinner fa-pulse");
});
});
});
// update history on tab state and implement navigation
if (window.location.hash != "") {
$('a[href="' + window.location.hash + '"]').click();
} else {
$('a[href="#aliases"]').click();
}
$('.nav-tabs a').on('shown.bs.tab', function (e) {
history.pushState(null, null, e.target.hash);
});
});
</script>
<section class="page-content-main">
<div class="container-fluid">
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
<li><a data-toggle="tab" href="#aliases" id="aliases_tab">{{ lang._('Aliases') }}</a></li>
<li><a data-toggle="tab" href="#geoip" id="geoip_tab">{{ lang._('GeopIP settings') }}</a></li>
</ul>
<div class="tab-content content-box">
<div id="aliases" class="tab-pane fade in">
<div class="row">
<section class="col-xs-12">
<div class="content-box">
@ -380,21 +404,27 @@
</tr>
</tfoot>
</table>
<div class="col-md-12">
<div id="aliasChangeMessage" class="alert alert-info" style="display: none" role="alert">
{{ lang._('After changing settings, please remember to apply them with the button below') }}
</div>
<hr/>
<button class="btn btn-primary" id="reconfigureAct" type="button"><b>{{ lang._('Apply') }}</b> <i id="reconfigureAct_progress" class=""></i></button>
<br/><br/>
</div>
</div>
</section>
</div>
</div>
<div id="geoip" class="tab-pane fade in">
{{ partial("layout_partials/base_form",['fields':formGeoIPSettings,'id':'frm_GeopIPSettings'])}}
</div>
</div>
<section class="page-content-main">
<div class="content-box">
<div class="col-md-12">
<br/>
<div id="aliasChangeMessage" class="alert alert-info" style="display: none" role="alert">
{{ lang._('After changing settings, please remember to apply them with the button below') }}
</div>
<button class="btn btn-primary" id="reconfigureAct" type="button"><b>{{ lang._('Apply') }}</b> <i id="reconfigureAct_progress" class=""></i></button>
<br/><br/>
</div>
</div>
</section>
{# Edit dialog #}
<div class="modal fade" id="DialogAlias" tabindex="-1" role="dialog" aria-labelledby="DialogAliasLabel" aria-hidden="true">
<div class="modal-backdrop fade in"></div>

View File

@ -32,4 +32,4 @@
from lib.geoip import download_geolite
# output files and lines processed
print ('%d files written, with a total number of %d lines' % download_geolite())
print ('%(file_count)d files written, with a total number of %(address_count)d lines' % download_geolite())

View File

@ -172,7 +172,7 @@ class Alias(object):
if (time.time() - fstat.st_mtime) < (86400 - 90):
do_update = False
if do_update:
syslog.syslog(syslog.LOG_ERR, 'geoip updated (files: %s lines: %s)' % geoip.download_geolite())
syslog.syslog(syslog.LOG_ERR, 'geoip updated (files: %(file_count)d lines: %(address_count)d)' % geoip.download_geolite())
for proto in self._proto.split(','):
geoip_filename = "/usr/local/share/GeoIP/alias/%s-%s" % (geoitem, proto)

View File

@ -27,6 +27,7 @@
download maxmind GeoLite2 Free database into easy to use alias files [<COUNTRY>-<PROTO>] located
in /usr/local/share/GeoIP/alias
"""
import datetime
import tempfile
import subprocess
import os
@ -34,52 +35,65 @@ import sys
import ujson
import requests
import zipfile
from configparser import ConfigParser
def download_geolite():
# define geoip download location
url = 'https://geolite.maxmind.com/download/geoip/database/GeoLite2-Country-CSV.zip'
updater_conf='/usr/local/etc/filter_geoip.conf'
stats_output = '/usr/local/share/GeoIP/alias.stats'
url = None
if os.path.exists(updater_conf):
cnf = ConfigParser()
cnf.read(updater_conf)
if cnf.has_section('settings') and cnf.has_option('settings', 'url'):
url = cnf.get('settings', 'url').strip()
address_count = 0
file_count = 0
# flush data from remote url to temp file and unpack from there
with tempfile.NamedTemporaryFile() as tmp_stream:
r = requests.get(url)
if r.status_code == 200:
tmp_stream.write(r.content)
tmp_stream.seek(0)
with zipfile.ZipFile(tmp_stream, mode='r', compression=zipfile.ZIP_DEFLATED) as zf:
# fetch zip file contents
file_handles = dict()
for item in zf.infolist():
if item.file_size > 0:
file_handles[os.path.basename(item.filename)] = item
# only process geo ip data when archive contains country definitions
if 'GeoLite2-Country-Locations-en.csv' in file_handles:
country_codes = dict()
# parse geoname_id to country code map
for line in zf.open(file_handles['GeoLite2-Country-Locations-en.csv']).read().decode().split('\n'):
parts = line.split(',')
if len(parts) > 4 and len(parts[4]) >= 1 and len(parts[4]) <= 3:
country_codes[parts[0]] = parts[4]
# process all details into files per country / protocol
for proto in ['IPv4', 'IPv6']:
if 'GeoLite2-Country-Blocks-%s.csv' % proto in file_handles:
output_handles = dict()
country_blocks = zf.open(file_handles['GeoLite2-Country-Blocks-%s.csv' % proto]).read()
for line in country_blocks.decode().split('\n'):
parts = line.split(',')
if len(parts) > 3 and parts[1] in country_codes:
country_code = country_codes[parts[1]]
if country_code not in output_handles:
if not os.path.exists('/usr/local/share/GeoIP/alias'):
os.makedirs('/usr/local/share/GeoIP/alias')
output_handles[country_code] = open(
'/usr/local/share/GeoIP/alias/%s-%s'%(country_code,proto), 'w'
)
file_count += 1
output_handles[country_code].write("%s\n" % parts[0])
address_count += 1
for country_code in output_handles:
output_handles[country_code].close()
result = {'address_count': 0 , 'file_count': 0, 'timestamp': None}
if url is not None:
# flush data from remote url to temp file and unpack from there
with tempfile.NamedTemporaryFile() as tmp_stream:
r = requests.get(url)
if r.status_code == 200:
tmp_stream.write(r.content)
tmp_stream.seek(0)
with zipfile.ZipFile(tmp_stream, mode='r', compression=zipfile.ZIP_DEFLATED) as zf:
# fetch zip file contents
file_handles = dict()
for item in zf.infolist():
if item.file_size > 0:
file_handles[os.path.basename(item.filename)] = item
# only process geo ip data when archive contains country definitions
if 'GeoLite2-Country-Locations-en.csv' in file_handles:
dt = datetime.datetime(*file_handles['GeoLite2-Country-Locations-en.csv'].date_time).isoformat()
result['timestamp'] = dt
country_codes = dict()
# parse geoname_id to country code map
for line in zf.open(file_handles['GeoLite2-Country-Locations-en.csv']).read().decode().split('\n'):
parts = line.split(',')
if len(parts) > 4 and len(parts[4]) >= 1 and len(parts[4]) <= 3:
country_codes[parts[0]] = parts[4]
# process all details into files per country / protocol
for proto in ['IPv4', 'IPv6']:
if 'GeoLite2-Country-Blocks-%s.csv' % proto in file_handles:
output_handles = dict()
country_blocks = zf.open(file_handles['GeoLite2-Country-Blocks-%s.csv' % proto]).read()
for line in country_blocks.decode().split('\n'):
parts = line.split(',')
if len(parts) > 3 and parts[1] in country_codes:
country_code = country_codes[parts[1]]
if country_code not in output_handles:
if not os.path.exists('/usr/local/share/GeoIP/alias'):
os.makedirs('/usr/local/share/GeoIP/alias')
output_handles[country_code] = open(
'/usr/local/share/GeoIP/alias/%s-%s'%(country_code,proto), 'w'
)
result['file_count'] += 1
output_handles[country_code].write("%s\n" % parts[0])
result['address_count'] += 1
for country_code in output_handles:
output_handles[country_code].close()
return (file_count, address_count)
open(stats_output,'w').write(ujson.dumps(result))
return result

View File

@ -1 +1,2 @@
filter_tables.conf:/usr/local/etc/filter_tables.conf
filter_geoip.conf:/usr/local/etc/filter_geoip.conf

View File

@ -0,0 +1,6 @@
[settings]
{% if helpers.exists('OPNsense.Firewall.Alias.geoip.url') %}
url={{OPNsense.Firewall.Alias.geoip.url}}
{% else %}
url=
{% endif %}