Merge pull request #811 from opnsense/nginx-request-limits

www/nginx: add request limits
This commit is contained in:
Fabian Franz BSc
2018-09-03 17:19:57 +02:00
committed by GitHub
12 changed files with 319 additions and 4 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
PLUGIN_NAME= nginx
PLUGIN_VERSION= 1.0
PLUGIN_VERSION= 1.1
PLUGIN_COMMENT= Nginx HTTP server and reverse proxy
PLUGIN_DEPENDS= nginx
PLUGIN_MAINTAINER= franz.fabian.94@gmail.com
@@ -305,4 +305,60 @@ class SettingsController extends ApiMutableModelControllerBase
{
return $this->setBase('security_header', 'security_header', $uuid);
}
// access limit zone headers
public function searchlimit_zoneAction()
{
return $this->searchBase('limit_zone',
array('description', 'key', 'size', 'rate', 'rate_unit'));
}
public function getlimit_zoneAction($uuid = null)
{
$this->sessionClose();
return $this->getBase('limit_zone', 'limit_zone', $uuid);
}
public function addlimit_zoneAction()
{
return $this->addBase('limit_zone', 'limit_zone');
}
public function dellimit_zoneAction($uuid)
{
return $this->delBase('limit_zone', $uuid);
}
public function setlimit_zoneAction($uuid)
{
return $this->setBase('limit_zone', 'limit_zone', $uuid);
}
// limit_request_connection
public function searchlimit_request_connectionAction()
{
return $this->searchBase('limit_request_connection',
array('description', 'limit_zone', 'nodelay', 'burst', 'connection_count'));
}
public function getlimit_request_connectionAction($uuid = null)
{
$this->sessionClose();
return $this->getBase('limit_request_connection', 'limit_request_connection', $uuid);
}
public function addlimit_request_connectionAction()
{
return $this->addBase('limit_request_connection', 'limit_request_connection');
}
public function dellimit_request_connectionAction($uuid)
{
return $this->delBase('limit_request_connection', $uuid);
}
public function setlimit_request_connectionAction($uuid)
{
return $this->setBase('limit_request_connection', 'limit_request_connection', $uuid);
}
}
@@ -54,6 +54,8 @@ class IndexController extends \OPNsense\Base\IndexController
$this->view->naxsi_rule = $this->getForm("naxsi_rule");
$this->view->naxsi_custom_policy = $this->getForm("naxsi_custom_policy");
$this->view->security_headers = $this->getForm("security_headers");
$this->view->limit_request_connection = $this->getForm("limit_request_connection");
$this->view->limit_zone = $this->getForm("limit_zone");
$this->view->pick('OPNsense/Nginx/index');
}
@@ -94,4 +94,11 @@
<label>Security Header</label>
<type>dropdown</type>
</field>
<field>
<id>httpserver.limit_request_connections</id>
<label>Limit Requests</label>
<type>select_multiple</type>
<style>selectpicker</style>
<help>If you choose multiple limits, the strictest will be used.</help>
</field>
</form>
@@ -0,0 +1,27 @@
<form>
<field>
<id>limit_request_connection.description</id>
<label>Description</label>
<type>text</type>
</field>
<field>
<id>limit_request_connection.limit_zone</id>
<label>Limit Zone</label>
<type>dropdown</type>
</field>
<field>
<id>limit_request_connection.connection_count</id>
<label>Connection Count (Streams Only)</label>
<type>text</type>
</field>
<field>
<id>limit_request_connection.burst</id>
<label>Burst (HTTP Only)</label>
<type>text</type>
</field>
<field>
<id>limit_request_connection.nodelay</id>
<label>No Delay (HTTP Only)</label>
<type>text</type>
</field>
</form>
@@ -0,0 +1,27 @@
<form>
<field>
<id>limit_zone.description</id>
<label>Description</label>
<type>text</type>
</field>
<field>
<id>limit_zone.key</id>
<label>Key</label>
<type>dropdown</type>
</field>
<field>
<id>limit_zone.size</id>
<label>Size (MB)</label>
<type>text</type>
</field>
<field>
<id>limit_zone.rate</id>
<label>Rate</label>
<type>text</type>
</field>
<field>
<id>limit_zone.rate_unit</id>
<label>Rate Unit</label>
<type>dropdown</type>
</field>
</form>
@@ -61,6 +61,13 @@
<style>selectpicker</style>
<help>Select an upstream to proxy to or connect via FastCGI if chosen.</help>
</field>
<field>
<id>location.limit_request_connections</id>
<label>Limit Requests</label>
<type>select_multiple</type>
<style>selectpicker</style>
<help>If you choose multiple limits, the strictest will be used.</help>
</field>
<field>
<id>location.root</id>
<label>File System Root</label>
@@ -1,6 +1,6 @@
<model>
<mount>//OPNsense/Nginx</mount>
<version>1.1.0</version>
<version>1.1.1</version>
<description>nginx web server, reverse proxy and waf</description>
<items>
<general>
@@ -255,6 +255,18 @@
<php_override_scriptname type="TextField">
<Required>N</Required>
</php_override_scriptname>
<limit_request_connections type="ModelRelationField">
<Model>
<template>
<source>OPNsense.Nginx.Nginx</source>
<items>limit_request_connection</items>
<display>description</display>
</template>
</Model>
<ValidationMessage>Selected limit zone not found</ValidationMessage>
<Required>N</Required>
<multiple>Y</multiple>
</limit_request_connections>
</location>
<custom_policy type="ArrayField">
@@ -479,6 +491,18 @@
<Required>N</Required>
<multiple>N</multiple>
</security_header>
<limit_request_connections type="ModelRelationField">
<Model>
<template>
<source>OPNsense.Nginx.Nginx</source>
<items>limit_request_connection</items>
<display>description</display>
</template>
</Model>
<ValidationMessage>Selected limit zone not found</ValidationMessage>
<Required>N</Required>
<multiple>Y</multiple>
</limit_request_connections>
</http_server>
<http_rewrite type="ArrayField">
@@ -835,5 +859,68 @@
<default>0</default>
</csp_form_action_none>
</security_header>
<limit_zone type="ArrayField">
<description type="TextField">
<Required>Y</Required>
</description>
<key type="OptionField">
<default>binary_remote_addr</default>
<OptionValues>
<binary_remote_addr>Remote IP Address</binary_remote_addr>
</OptionValues>
<Required>Y</Required>
</key>
<rate_unit type="OptionField">
<default>r/s</default>
<OptionValues>
<val1 value="r/s">Requests Per Second</val1>
<val2 value="r/m">Requests Per Minute</val2>
</OptionValues>
<Required>Y</Required>
</rate_unit>
<size type="IntegerField">
<Required>Y</Required>
<default>10</default>
<MinimumValue>1</MinimumValue>
</size>
<rate type="IntegerField">
<Required>Y</Required>
<default>20</default>
<MinimumValue>1</MinimumValue>
</rate>
</limit_zone>
<limit_request_connection type="ArrayField">
<limit_zone type="ModelRelationField">
<Model>
<template>
<source>OPNsense.Nginx.Nginx</source>
<items>limit_zone</items>
<display>description</display>
</template>
</Model>
<ValidationMessage>Selected limit zone not found</ValidationMessage>
<Required>Y</Required>
<multiple>Y</multiple>
</limit_zone>
<connection_count type="IntegerField">
<MinimumValue>1</MinimumValue>
<Required>Y</Required>
<default>5</default>
</connection_count>
<burst type="IntegerField">
<MinimumValue>1</MinimumValue>
<Required>N</Required>
<default>20</default>
</burst>
<nodelay type="BooleanField">
<default>1</default>
<Required>Y</Required>
</nodelay>
<description type="TextField">
<Required>Y</Required>
</description>
</limit_request_connection>
</items>
</model>
@@ -97,6 +97,8 @@ $( document ).ready(function() {
'httprewrite',
'custompolicy',
'security_header',
'limit_zone',
'limit_request_connection',
'naxsirule'].forEach(function(element) {
$("#grid-" + element).UIBootgrid(
{ 'search':'/api/nginx/settings/search' + element,
@@ -118,10 +120,14 @@ $( document ).ready(function() {
<ul class="nav nav-tabs" role="tablist" id="maintabs">
{{ partial("layout_partials/base_tabs_header",['formData':settings]) }}
<li role="presentation" class="dropdown">
<a data-toggle="dropdown" href="#" class="dropdown-toggle pull-right visible-lg-inline-block visible-md-inline-block visible-xs-inline-block visible-sm-inline-block" role="button">
<a data-toggle="dropdown" href="#"
class="dropdown-toggle pull-right visible-lg-inline-block visible-md-inline-block visible-xs-inline-block visible-sm-inline-block"
role="button">
<b><span class="caret"></span></b>
</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>
<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>
<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>
@@ -155,6 +161,25 @@ $( document ).ready(function() {
</li>
</ul>
</li>
<li role="presentation" class="dropdown">
<a data-toggle="dropdown"
href="#"
class="dropdown-toggle pull-right visible-lg-inline-block visible-md-inline-block visible-xs-inline-block visible-sm-inline-block"
role="button">
<b><span class="caret"></span></b>
</a>
<a data-toggle="tab" onclick="$('#subtab_item_nginx-access-request-limit').click();"
class="visible-lg-inline-block visible-md-inline-block visible-xs-inline-block visible-sm-inline-block"
style="border-right:0px;"><b>{{ lang._('Access')}}</b></a>
<ul class="dropdown-menu" role="menu">
<li>
<a data-toggle="tab" id="subtab_item_nginx-access-request-limit" href="#subtab_nginx-access-request-limit">{{ lang._('Limit Zone')}}</a>
</li>
<li>
<a data-toggle="tab" id="subtab_item_nginx-access-request-limit-connection" href="#subtab_nginx-access-request-limit-connection">{{ lang._('Connection Limits')}}</a>
</li>
</ul>
</li>
</ul>
<div class="content-box tab-content">
@@ -392,6 +417,56 @@ $( document ).ready(function() {
</tfoot>
</table>
</div>
<div id="subtab_nginx-access-request-limit" class="tab-pane fade">
<table id="grid-limit_zone" class="table table-condensed table-hover table-striped table-responsive" data-editDialog="limit_zonedlg">
<thead>
<tr>
<th data-column-id="description" data-type="string" data-sortable="true" data-visible="true">{{ lang._('Description') }}</th>
<th data-column-id="key" data-type="string" data-sortable="true" data-visible="true">{{ lang._('Key') }}</th>
<th data-column-id="size" data-type="string" data-sortable="true" data-visible="true">{{ lang._('Size') }}</th>
<th data-column-id="rate" data-type="string" data-sortable="true" data-visible="true">{{ lang._('Rate') }}</th>
<th data-column-id="rate_unit" data-type="string" data-sortable="true" data-visible="true">{{ lang._('Rate Unit') }}</th>
<th data-column-id="commands" data-width="7em" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<td></td>
<td>
<button data-action="add" type="button" class="btn btn-xs btn-default"><span class="fa fa-plus"></span></button>
<button type="button" class="btn btn-xs reload_btn btn-primary"><span class="fa fa-refresh reloadAct_progress"></span></button>
</td>
</tr>
</tfoot>
</table>
</div>
<div id="subtab_nginx-access-request-limit-connection" class="tab-pane fade">
<table id="grid-limit_request_connection" class="table table-condensed table-hover table-striped table-responsive" data-editDialog="limit_request_connectiondlg">
<thead>
<tr>
<th data-column-id="description" data-type="string" data-sortable="true" data-visible="true">{{ lang._('Description') }}</th>
<th data-column-id="limit_zone" data-type="string" data-sortable="true" data-visible="true">{{ lang._('Limit Zone') }}</th>
<th data-column-id="connection_count" data-type="string" data-sortable="true" data-visible="true">{{ lang._('Connection Count') }}</th>
<th data-column-id="burst" data-type="string" data-sortable="true" data-visible="true">{{ lang._('Burst') }}</th>
<th data-column-id="nodelay" data-type="string" data-sortable="true" data-visible="true">{{ lang._('No Delay') }}</th>
<th data-column-id="commands" data-width="7em" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<td></td>
<td>
<button data-action="add" type="button" class="btn btn-xs btn-default"><span class="fa fa-plus"></span></button>
<button type="button" class="btn btn-xs reload_btn btn-primary"><span class="fa fa-refresh reloadAct_progress"></span></button>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
@@ -406,3 +481,5 @@ $( document ).ready(function() {
{{ partial("layout_partials/base_dialog",['fields': naxsi_custom_policy,'id':'custompolicydlg', 'label':lang._('Edit WAF Policy')]) }}
{{ partial("layout_partials/base_dialog",['fields': naxsi_rule,'id':'naxsiruledlg', 'label':lang._('Edit Naxsi Rule')]) }}
{{ partial("layout_partials/base_dialog",['fields': security_headers,'id':'security_headersdlg', 'label':lang._('Edit Security Headers')]) }}
{{ partial("layout_partials/base_dialog",['fields': limit_request_connection,'id':'limit_request_connectiondlg', 'label':lang._('Edit Request Connection Limit')]) }}
{{ partial("layout_partials/base_dialog",['fields': limit_zone,'id':'limit_zonedlg', 'label':lang._('Edit Limit Zone')]) }}
@@ -32,6 +32,11 @@ keepalive_timeout {{ OPNsense.Nginx.http.keepalive_timeout }};
# TODO add when core is ready for allowing nginx to serve the web interface
# include nginx_web.conf;
{% for zone in helpers.toList('OPNsense.Nginx.limit_zone') %}
limit_req_zone ${{ zone.key }} zone={{ zone['@uuid'].replace('-', '') }}:{{ zone.size }}m rate={{ zone.rate }}{{ zone.rate_unit }};
{% endfor %}
{% include "OPNsense/Nginx/upstream.conf" ignore missing with context %}
{% set listen_list = [] %}
@@ -91,6 +96,10 @@ server {
{% if security_rule is defined %}
{% include "OPNsense/Nginx/security_rule.conf" ignore missing with context %}
{% endif %}
{% endif %}
{% if server.limit_request_connections is defined %}
{% set limit_request = server.limit_request_connections %}
{% include "OPNsense/Nginx/httprequestlimit.conf" ignore missing with context %}
{% endif %}
# location to ban the host permanently
set $naxsi_extensive_log {% if server.naxsi_extensive_log is defined and server.naxsi_extensive_log == '1' %}1{% else %}0{% endif %};
@@ -0,0 +1,12 @@
{% if limit_request is defined and limit_request != '' %}
{% for req in limit_request.split(',') %}
{% if helpers.getUUID(req) is defined %}
{% set req_data = helpers.getUUID(req) %}
{% if helpers.getUUID(req_data.limit_zone) is defined %}
limit_req zone={{ req_data.limit_zone.replace('-', '')
}}{% if req_data.burst is defined and req_data.burst != '' %} burst={{ req_data.burst
}}{% if req_data.nodelay is defined and req_data.nodelay == '1' %} nodelay{% endif %}{% endif %};
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
@@ -47,6 +47,10 @@ location {{ location.matchtype }} {{ location.urlpattern }} {
{% else %}
autoindex off;
{% endif %}
{% if location.limit_request_connections is defined %}
{% set limit_request = location.limit_request_connections %}
{% include "OPNsense/Nginx/httprequestlimit.conf" ignore missing with context %}
{% endif %}
{% if location.authbasic is defined and location.authbasicuserfile is defined %}
auth_basic "{{location.authbasic}}";
auth_basic_user_file /var/db/nginx/auth/{{ location.authbasicuserfile }};