www/nginx: import naxsi core rules into configuration (#891)

* www/nginx: add draft of code to download naxsi rules
* www/nginx: run bmake style-fix
* www/nginx: naxsi rule download
* www/nginx: rename file
* www/nginx: add configd task
* www/nginx: add initial GUI code for rule download
* www/nginx: add view part
* www/nginx: fix inverted regex flag
* Problem with multiple Upstream servers (#893)

Small change to make it work.
This commit is contained in:
Fabian Franz BSc
2018-10-07 22:58:18 +02:00
committed by GitHub
parent 9d006edd2c
commit a2c119ef97
6 changed files with 232 additions and 6 deletions
@@ -29,12 +29,23 @@
namespace OPNsense\Nginx\Api;
use OPNsense\Base\ApiMutableModelControllerBase;
use OPNsense\Core\Backend;
class SettingsController extends ApiMutableModelControllerBase
{
static protected $internalModelClass = '\OPNsense\Nginx\Nginx';
static protected $internalModelName = 'nginx';
// download rules
public function downloadrulesAction()
{
if (!$this->request->isPost()) {
return array('error' => 'Must be called via POST');
}
$backend = new Backend();
return array('result' => trim($backend->configdRun('nginx naxsidownloadrules')));
}
// User List
public function searchuserlistAction()
@@ -57,6 +57,9 @@ class IndexController extends \OPNsense\Base\IndexController
$this->view->limit_request_connection = $this->getForm("limit_request_connection");
$this->view->limit_zone = $this->getForm("limit_zone");
$this->view->cache_path = $this->getForm("cache_path");
$nginx = new Nginx();
$this->view->show_naxsi_download_button =
count($nginx->custom_policy->__items) == 0 && count($nginx->naxsi_rule->__items) == 0;
$this->view->pick('OPNsense/Nginx/index');
}
@@ -38,7 +38,7 @@ $( document ).ready(function() {
});
// update history on tab state and implement navigation
if(window.location.hash != "") {
if(window.location.hash !== "") {
$('a[href="' + window.location.hash + '"]').click()
}
$('.nav-tabs a').on('shown.bs.tab', function (e) {
@@ -56,8 +56,8 @@ $( document ).ready(function() {
// form save event handlers for all defined forms
$('[id*="save_"]').each(function(){
$(this).click(function() {
var frm_id = $(this).closest("form").attr("id");
var frm_title = $(this).closest("form").attr("data-title");
let frm_id = $(this).closest("form").attr("id");
let frm_title = $(this).closest("form").attr("data-title");
// save data for General TAB
saveFormToEndpoint(url="/api/nginx/settings/set", formid=frm_id, callback_ok=function(){
// on correct save, perform reconfigure. set progress animation when reloading
@@ -67,7 +67,7 @@ $( document ).ready(function() {
// when done, disable progress animation.
$("#"+frm_id+"_progress").removeClass("fa fa-spinner fa-pulse");
if (data != undefined && (status != "success" || data['status'] != 'ok')) {
if (data !== undefined && (status !== "success" || data['status'] !== 'ok')) {
// fix error handling
BootstrapDialog.show({
type:BootstrapDialog.TYPE_WARNING,
@@ -105,6 +105,33 @@ $( document ).ready(function() {
}
);
});
let naxsi_rule_download_button = $('#naxsiruledownloadbtn');
naxsi_rule_download_button.click(function () {
BootstrapDialog.show({
type: BootstrapDialog.TYPE_INFO,
title: "{{ lang._('Download NAXSI Rules') }}",
message: "{{ lang._('You are about to download the core rules from the Repository of NAXSI. You have to accept its %slicense%s to download the rules.')|format("<a href='https://github.com/nbs-system/naxsi/blob/master/LICENSE' target='_blank'>", "</a>") }}",
buttons: [{
label: "{{ lang._('Accept And Download') }}",
cssClass: 'btn-primary',
icon: 'fa fa-download',
action: function(dlg){
dlg.close();
ajaxCall(url="/api/nginx/settings/downloadrules", sendData={}, callback=function(data,status) {
$('#naxsiruledownloadalert').hide();
// reload view after installing rules
$('#grid-naxsirule').bootgrid('reload');
$('#grid-custompolicy').bootgrid('reload');
});
}
}, {
label: '{{ lang._('Reject') }}',
action: function(dlg){
dlg.close();
}
}]
});
});
});
@@ -122,7 +149,7 @@ $( document ).ready(function() {
</a>
<a data-toggle="tab" onclick="$('#subtab_item_nginx-http-location').click();"
class="visible-lg-inline-block visible-md-inline-block visible-xs-inline-block visible-sm-inline-block"
style="border-right:0px;"><b>{{ lang._('HTTP(S)')}}</b></a>
style="border-right:0;"><b>{{ lang._('HTTP(S)')}}</b></a>
<ul class="dropdown-menu" role="menu">
<li>
<a data-toggle="tab" id="subtab_item_nginx-http-location" href="#subtab_nginx-http-location">{{ lang._('Location')}}</a>
@@ -350,6 +377,16 @@ $( document ).ready(function() {
</table>
</div>
<div id="subtab_nginx-http-custompolicy" class="tab-pane fade">
{% if (show_naxsi_download_button) %}
<div class="alert alert-info" id="naxsiruledownloadalert" role="alert" style="vertical-align: middle;display: table;width: 100%;">
<div style="display: table-cell;vertical-align: middle;">{{ lang._('It looks like you are not having any rules installed. You may want to download the NAXSI core rules.') }}</div>
<div class="pull-right" style="vertical-align: middle;display: table-cell;">
<button id="naxsiruledownloadbtn" class="btn btn-primary">
<i class="fa fa-download" aria-hidden="true"></i> {{ lang._('Download') }}
</button>
</div>
</div>
{% endif %}
<table id="grid-custompolicy" class="table table-condensed table-hover table-striped table-responsive" data-editDialog="custompolicydlg">
<thead>
<tr>
@@ -0,0 +1,170 @@
#!/usr/local/bin/php
<?php
/**
* Copyright (C) 2018 Fabian Franz
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
require_once('config.inc');
use OPNsense\Core\Config;
use OPNsense\Nginx\Nginx;
function download_rules()
{
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://raw.githubusercontent.com/nbs-system/naxsi/master/naxsi_config/naxsi_core.rules',
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_VERBOSE => 0,
CURLOPT_MAXREDIRS => 1,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_HTTPHEADER => array(
"User-Agent: OPNsense Firewall"
)
));
$response = curl_exec($curl);
$err = curl_error($curl);
$info = curl_getinfo($curl);
curl_close($curl);
if ($info['http_code'] != 200 || $err) {
syslog(LOG_ERR, 'Cannot download NAXSI core rules');
syslog(LOG_ERR, json_encode($info));
exit(1);
}
return $response;
}
function prepare_values($row)
{
$row['match_zone'] = explode('|', $row['match_zone']);
return $row;
}
function parse_rules($data)
{
$parsed = [];
$tmp = null;
$description = array('rule', 'match_type', 'match', 'message', 'match_zone', 'variable', 'value', 'id');
foreach ($data as $line) {
$line = trim($line);
$matches = [];
if (preg_match('/## (.*) ##/', $line, $matches)) {
if (isset($tmp) && empty($parsed[$tmp])) {
unset($parsed[$tmp]);
}
$tmp = trim($matches[1]);
$parsed[$tmp] = [];
} elseif (preg_match('/\S+ "(str|rx):([^\"]+)" "msg:([^\\"]*)" "mz:([^\"]*)" "s:([^\"]*):(\d+)" id:(\d+);/', $line, $matches)) {
$parsed[$tmp][] = prepare_values(array_combine($description, $matches));
}
}
return $parsed;
}
function save_to_model($data)
{
$model = new Nginx();
foreach ($data as $group => $rules) {
// create a new policy
$policy = $model->custom_policy->Add();
$policy->name = $group;
$policy->value = '8';
$policy->operator = '>=';
$policy->action = 'BLOCK';
// create new values for policy
$rule_list = [];
foreach ($rules as $rule) {
$rule_mdl = $model->naxsi_rule->Add();
$rule_mdl->description = $rule['message'];
$rule_mdl->message = $rule['message'];
$rule_mdl->ruletype = 'main';
$rule_mdl->match_type = 'id';
$rule_mdl->identifier = $rule['id'];
$rule_mdl->match_value = $rule['match'];
$rule_mdl->regex = $rule['match_type'] == 'str' ? '0' : '1';
// default to 0
$rule_mdl->args = '0';
$rule_mdl->headers = '0';
$rule_mdl->name = '0';
$rule_mdl->raw_body = '0';
$rule_mdl->file_extension = '0';
$rule_mdl->negate = '0';
foreach ($rule['match_zone'] as $match_zone) {
if (stripos($match_zone, ':') === false) {
switch ($match_zone) {
case 'ARGS':
$rule_mdl->args = '1';
break;
case 'HEADERS':
$rule_mdl->headers = '1';
break;
case 'NAME':
$rule_mdl->name = '1';
break;
case 'RAW_BODY':
$rule_mdl->raw_body = '1';
break;
case 'FILE_EXT':
$rule_mdl->file_extension = '1';
break;
}
} else {
$kv = explode(':', $match_zone);
switch ($kv[0]) {
case '$BODY_VAR':
$rule_mdl->dollar_body_var = $kv[1];
break;
case '$ARGS_VAR':
$rule_mdl->dollar_args_var = $kv[1];
break;
case '$HEADERS_VAR':
$rule_mdl->dollar_headers_var = $kv[1];
}
}
}
$rule_list[] = $rule_mdl->getAttributes()["uuid"];
}
$policy->naxsi_rules = implode(',', $rule_list);
}
$val_result = $model->performValidation(false);
if (count($val_result) !== 0) {
print_r($val_result);
exit(1);
}
$model->serializeToConfig();
Config::getInstance()->save();
}
#$data = parse_rules(file('./naxsi_core.rules'));
$data = parse_rules(explode("\n", download_rules()));
save_to_model($data);
@@ -50,3 +50,8 @@ parameters:
type:script_output
message:Ban bots and other attackers (nginx plugin)
description:Automatically ban attacking hosts (nginx plugin)
[naxsidownloadrules]
command:/usr/local/opnsense/scripts/nginx/naxsi_rule_download.php
parameters:
type:script
@@ -6,8 +6,8 @@
{% endif %}
{% endfor %}
{% for upstream_uuid, upstream in upstreamlist.items() %}
{% for upstream_serveruuid in upstream.serverentries.split(',') %}
upstream upstream{{ upstream_uuid.replace('-','') }} {
{% for upstream_serveruuid in upstream.serverentries.split(',') %}
{% set upstream_server = helpers.getUUID(upstream_serveruuid) %}
server {% if ':' in upstream_server.server %}[{% endif %}{{ upstream_server.server }}{% if ':' in upstream_server.server %}]{% endif
%}{% if upstream_server.port is defined %}:{{ upstream_server.port }}{% endif