mirror of
https://github.com/opnsense/plugins.git
synced 2026-05-28 04:34:15 -04:00
www/nginx: version 1.5
This commit is contained in:
parent
2845096784
commit
107a7f40e3
49 changed files with 1968 additions and 221 deletions
|
|
@ -1,5 +1,5 @@
|
|||
PLUGIN_NAME= nginx
|
||||
PLUGIN_VERSION= 1.3
|
||||
PLUGIN_VERSION= 1.5
|
||||
PLUGIN_COMMENT= Nginx HTTP server and reverse proxy
|
||||
PLUGIN_DEPENDS= nginx
|
||||
PLUGIN_MAINTAINER= franz.fabian.94@gmail.com
|
||||
|
|
|
|||
|
|
@ -8,6 +8,35 @@ reuse, SSL offload and HTTP media streaming.
|
|||
Plugin Changelog
|
||||
================
|
||||
|
||||
1.5
|
||||
|
||||
* Add proxy options for ignore client abort and disabling buffering
|
||||
* Add logviewer support for streams
|
||||
* Fix charset is not defined bug (contributed by ccesario [1])
|
||||
* Add an existence check for locations
|
||||
* Add PROXY protocol for HTTP and Streams frontend
|
||||
* Add PROXY backend support for Streams
|
||||
* Add support for better whitelist rules in the WAF
|
||||
|
||||
[1] https://github.com/opnsense/plugins/pull/1035
|
||||
|
||||
1.4 (Development only)
|
||||
|
||||
* move upstreams from HTTP to their own menu because they are used for TCP load balancing as well
|
||||
* add TCP load balancing [1]
|
||||
* add support for IP based ACLs
|
||||
* add log rotation (contributed by Julius Cesar Camargo [2])
|
||||
* add support for satisfy, body size limitation
|
||||
* change: allow to disable internal bot protection (contributed by @fzoske) [3]
|
||||
* change: do not save when no change in the list happened to prevent filling the log history
|
||||
* fix: translate a german string in upstream server to english
|
||||
* replace headers instead of just adding our own (duplication issue #971), suppress X-Powered-By from Upstream [4]
|
||||
|
||||
[1] https://github.com/opnsense/plugins/pull/930
|
||||
[2] https://github.com/opnsense/plugins/pull/982
|
||||
[3] https://github.com/opnsense/plugins/pull/934
|
||||
[4] https://github.com/opnsense/plugins/issues/971
|
||||
|
||||
1.3
|
||||
|
||||
* bugfix: correctly set upstream header
|
||||
|
|
|
|||
|
|
@ -57,6 +57,28 @@ class LogsController extends ApiControllerBase
|
|||
return $this->call_configd('error', $uuid);
|
||||
}
|
||||
}
|
||||
public function stream_accessesAction($uuid = null)
|
||||
{
|
||||
$this->nginx = new Nginx();
|
||||
if (!isset($uuid)) {
|
||||
// emulate REST API -> /stream_accesses delivers a list of servers with access logs
|
||||
return $this->list_streams();
|
||||
} else {
|
||||
// emulate REST call for a specific log /stream_accesses/uuid
|
||||
return $this->call_configd_stream('streamaccess', $uuid);
|
||||
}
|
||||
}
|
||||
public function stream_errorsAction($uuid = null)
|
||||
{
|
||||
$this->nginx = new Nginx();
|
||||
if (!isset($uuid)) {
|
||||
// emulate REST API -> /stream_errors delivers a list of servers with error logs
|
||||
return $this->list_streams();
|
||||
} else {
|
||||
// emulate REST call for a specific log /stream_errors/uuid
|
||||
return $this->call_configd_stream('streamerror', $uuid);
|
||||
}
|
||||
}
|
||||
|
||||
private function call_configd($type, $uuid)
|
||||
{
|
||||
|
|
@ -68,19 +90,42 @@ class LogsController extends ApiControllerBase
|
|||
$data = $backend->configdRun('nginx log ' . $type . ' ' . $uuid);
|
||||
return json_decode($data, true);
|
||||
}
|
||||
private function call_configd_stream($type, $uuid)
|
||||
{
|
||||
if (!$this->stream_exists($uuid)) {
|
||||
$this->response->setStatusCode(404, "Not Found");
|
||||
}
|
||||
|
||||
$backend = new Backend();
|
||||
$data = $backend->configdRun('nginx log ' . $type . ' ' . $uuid);
|
||||
return json_decode($data, true);
|
||||
}
|
||||
|
||||
private function list_vhosts()
|
||||
{
|
||||
$data = [];
|
||||
foreach ($this->nginx->http_server->__items as $item) {
|
||||
foreach ($this->nginx->http_server->iterateItems() as $item) {
|
||||
$data[] = array('id' => $item->getAttributes()['uuid'], 'server_name' => (string)$item->servername);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
private function list_streams()
|
||||
{
|
||||
$data = [];
|
||||
foreach ($this->nginx->stream_server->iterateItems() as $item) {
|
||||
$data[] = array('id' => $item->getAttributes()['uuid'], 'port' => (string)$item->listen_port);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function vhost_exists($uuid)
|
||||
{
|
||||
$data = $this->nginx->getNodeByReference('http_server.'. $uuid);
|
||||
return isset($data);
|
||||
}
|
||||
private function stream_exists($uuid)
|
||||
{
|
||||
$data = $this->nginx->getNodeByReference('stream_server.'. $uuid);
|
||||
return isset($data);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -236,6 +236,33 @@ class SettingsController extends ApiMutableModelControllerBase
|
|||
return $this->setBase('httpserver', 'http_server', $uuid);
|
||||
}
|
||||
|
||||
// stream server
|
||||
public function searchstreamserverAction()
|
||||
{
|
||||
return $this->searchBase('stream_server', array('description', 'certificate', 'udp', 'listen_port'));
|
||||
}
|
||||
|
||||
public function getstreamserverAction($uuid = null)
|
||||
{
|
||||
$this->sessionClose();
|
||||
return $this->getBase('streamserver', 'stream_server', $uuid);
|
||||
}
|
||||
|
||||
public function addstreamserverAction()
|
||||
{
|
||||
return $this->addBase('streamserver', 'stream_server');
|
||||
}
|
||||
|
||||
public function delstreamserverAction($uuid)
|
||||
{
|
||||
return $this->delBase('stream_server', $uuid);
|
||||
}
|
||||
|
||||
public function setstreamserverAction($uuid)
|
||||
{
|
||||
return $this->setBase('streamserver', 'stream_server', $uuid);
|
||||
}
|
||||
|
||||
// naxsi rules
|
||||
public function searchnaxsiruleAction()
|
||||
{
|
||||
|
|
@ -405,4 +432,207 @@ class SettingsController extends ApiMutableModelControllerBase
|
|||
{
|
||||
return $this->setBase('cache_path', 'cache_path', $uuid);
|
||||
}
|
||||
|
||||
// SNI Forward
|
||||
public function searchsnifwdAction()
|
||||
{
|
||||
return $this->searchBase('sni_hostname_upstream_map', array('description'));
|
||||
}
|
||||
|
||||
public function getsnifwdAction($uuid = null)
|
||||
{
|
||||
$this->sessionClose();
|
||||
$base = $this->getBase('snihostname', 'sni_hostname_upstream_map', $uuid);
|
||||
return $this->convert_sni_fwd_for_client($base);
|
||||
}
|
||||
|
||||
public function addsnifwdAction()
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$this->regenerate_hostname_map(null);
|
||||
return $this->addBase('snihostname', 'sni_hostname_upstream_map');
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
public function delsnifwdAction($uuid)
|
||||
{
|
||||
$nginx = $this->getModel();
|
||||
$uuid_attached = $nginx->find_sni_hostname_upstream_map_entry_uuids($uuid);
|
||||
|
||||
$ret = $this->delBase('sni_hostname_upstream_map', $uuid);
|
||||
if ($ret['result'] == 'deleted') {
|
||||
foreach ($uuid_attached as $old_uuid) {
|
||||
$this->delBase('sni_hostname_upstream_map_item', $old_uuid);
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function setsnifwdAction($uuid)
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$this->regenerate_hostname_map($uuid);
|
||||
return $this->setBase('snihostname', 'sni_hostname_upstream_map', $uuid);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
// IP / Network based ACLs
|
||||
public function searchipaclAction()
|
||||
{
|
||||
return $this->searchBase('ip_acl', array('description'));
|
||||
}
|
||||
|
||||
public function getipaclAction($uuid = null)
|
||||
{
|
||||
$this->sessionClose();
|
||||
$base = $this->getBase('ipacl', 'ip_acl', $uuid);
|
||||
return $this->convert_ipacl_for_client($base);
|
||||
}
|
||||
|
||||
public function addipaclAction()
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$this->regenerate_ipacl(null);
|
||||
return $this->addBase('ipacl', 'ip_acl');
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
public function delipaclAction($uuid)
|
||||
{
|
||||
$nginx = $this->getModel();
|
||||
$uuid_attached = $nginx->find_ip_acl_entry_uuids($uuid);
|
||||
|
||||
$ret = $this->delBase('ip_acl', $uuid);
|
||||
if ($ret['result'] == 'deleted') {
|
||||
foreach ($uuid_attached as $old_uuid) {
|
||||
$this->delBase('ip_acl_item', $old_uuid);
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function setipaclAction($uuid)
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$this->regenerate_ipacl($uuid);
|
||||
return $this->setBase('ipacl', 'ip_acl', $uuid);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
/*
|
||||
* worker code starts here
|
||||
*/
|
||||
|
||||
private function convert_sni_fwd_for_client($response_data)
|
||||
{
|
||||
if (!isset($response_data['snihostname']['data'])) {
|
||||
return $response_data;
|
||||
}
|
||||
$nginx = $this->getModel();
|
||||
$uuids_map = explode(',', $response_data['snihostname']['data']);
|
||||
$response_data['snihostname']['data'] = [];
|
||||
foreach ($uuids_map as $uuid_line) {
|
||||
$rowdata = $nginx->getNodeByReference('sni_hostname_upstream_map_item.' . $uuid_line);
|
||||
if ($rowdata != null) {
|
||||
$response_data['snihostname']['data'][] =
|
||||
array('hostname' => (string)$rowdata->hostname,
|
||||
'upstream' => (string)$rowdata->upstream);
|
||||
}
|
||||
}
|
||||
return $response_data;
|
||||
}
|
||||
private function convert_ipacl_for_client($response_data)
|
||||
{
|
||||
if (!isset($response_data['ipacl']['data'])) {
|
||||
return $response_data;
|
||||
}
|
||||
$nginx = $this->getModel();
|
||||
$uuids_map = explode(',', $response_data['ipacl']['data']);
|
||||
$response_data['ipacl']['data'] = [];
|
||||
foreach ($uuids_map as $uuid_line) {
|
||||
$rowdata = $nginx->getNodeByReference('ip_acl_item.' . $uuid_line);
|
||||
if ($rowdata != null) {
|
||||
$response_data['ipacl']['data'][] =
|
||||
array('network' => (string)$rowdata->network,
|
||||
'action' => (string)$rowdata->action);
|
||||
}
|
||||
}
|
||||
return $response_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null $uuid the uuid which should get cleared before
|
||||
* @throws \ReflectionException if the model was not found
|
||||
* @throws \Phalcon\Validation\Exception on validation errors
|
||||
*/
|
||||
private function regenerate_hostname_map($uuid = null)
|
||||
{
|
||||
$nginx = $this->getModel();
|
||||
if ($this->request->hasPost('snihostname') && is_array($_POST['snihostname']['data'])) {
|
||||
if ($uuid != null) {
|
||||
// for an update, we have to clear it.
|
||||
$this->delete_uuids(
|
||||
$nginx->find_sni_hostname_upstream_map_entry_uuids($uuid),
|
||||
'sni_hostname_upstream_map_item'
|
||||
);
|
||||
}
|
||||
$ids = [];
|
||||
$postdata = $_POST['snihostname']['data'];
|
||||
foreach ($postdata as $post_item) {
|
||||
$item = $nginx->sni_hostname_upstream_map_item->Add();
|
||||
$ids[] = $item->getAttributes()['uuid'];
|
||||
$item->hostname = $post_item['hostname'];
|
||||
$item->upstream = $post_item['upstream'];
|
||||
}
|
||||
$nginx->serializeToConfig();
|
||||
$_POST['snihostname']['data'] = implode(',', $ids);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null $uuid the uuid which should get cleared before
|
||||
* @throws \ReflectionException if the model was not found
|
||||
* @throws \Phalcon\Validation\Exception on validation errors
|
||||
*/
|
||||
private function regenerate_ipacl($uuid = null)
|
||||
{
|
||||
$nginx = $this->getModel();
|
||||
if ($this->request->hasPost('ipacl') && is_array($_POST['ipacl']['data'])) {
|
||||
if ($uuid != null) {
|
||||
// for an update, we have to clear it.
|
||||
$this->delete_uuids(
|
||||
$nginx->find_ip_acl_uuids($uuid),
|
||||
'ip_acl_item'
|
||||
);
|
||||
}
|
||||
$ids = [];
|
||||
$postdata = $_POST['ipacl']['data'];
|
||||
foreach ($postdata as $post_item) {
|
||||
$item = $nginx->ip_acl_item->Add();
|
||||
$ids[] = $item->getAttributes()['uuid'];
|
||||
$item->network = $post_item['network'];
|
||||
$item->action = $post_item['action'];
|
||||
}
|
||||
$nginx->serializeToConfig();
|
||||
$_POST['ipacl']['data'] = implode(',', $ids);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $uuids array list of UUIDs
|
||||
* @param $path string the model prefix from the element to delete
|
||||
* @throws \Phalcon\Validation\Exception
|
||||
*/
|
||||
private function delete_uuids($uuids, $path): void
|
||||
{
|
||||
foreach ($uuids as $item_uuid) {
|
||||
try {
|
||||
$this->delBase($path, $item_uuid);
|
||||
} catch (\Exception $e) {
|
||||
// we don't care about then.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ class IndexController extends \OPNsense\Base\IndexController
|
|||
$this->view->credential = $this->getForm("credential");
|
||||
$this->view->userlist = $this->getForm("userlist");
|
||||
$this->view->httpserver = $this->getForm("httpserver");
|
||||
$this->view->streamserver = $this->getForm("streamserver");
|
||||
$this->view->httprewrite = $this->getForm("httprewrite");
|
||||
$this->view->naxsi_rule = $this->getForm("naxsi_rule");
|
||||
$this->view->naxsi_custom_policy = $this->getForm("naxsi_custom_policy");
|
||||
|
|
@ -57,9 +58,11 @@ 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");
|
||||
$this->view->sni_hostname_map = $this->getForm("sni_hostname_map");
|
||||
$this->view->ipacl = $this->getForm("ipacl");
|
||||
$nginx = new Nginx();
|
||||
$this->view->show_naxsi_download_button =
|
||||
count($nginx->custom_policy->__items) == 0 && count($nginx->naxsi_rule->__items) == 0;
|
||||
count($nginx->custom_policy->iterateItems()) == 0 && count($nginx->naxsi_rule->iterateItems()) == 0;
|
||||
$this->view->pick('OPNsense/Nginx/index');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,30 @@
|
|||
<label>HTTPS Listen Port</label>
|
||||
<type>text</type>
|
||||
</field>
|
||||
<field>
|
||||
<id>httpserver.proxy_protocol</id>
|
||||
<label>PROXY Protocol</label>
|
||||
<type>checkbox</type>
|
||||
<advanced>true</advanced>
|
||||
<help>If you enable the proxy protocol, a downstream proxy can send the client IP and port before the real traffic is set.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>httpserver.trusted_proxies</id>
|
||||
<label>Trusted Proxies</label>
|
||||
<allownew>true</allownew>
|
||||
<style>tokenize</style>
|
||||
<type>select_multiple</type>
|
||||
<advanced>true</advanced>
|
||||
<help>Enter a list of IP addresses or CIDR networks which are allowed to override the source IP address using the specified header.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>httpserver.real_ip_source</id>
|
||||
<label>Real IP Source</label>
|
||||
<style>selectpicker</style>
|
||||
<advanced>true</advanced>
|
||||
<help>X-Real-IP and X-Forwarded-For are HTTP headers, while PROXY protocol is a protocol which needs to be enabled.</help>
|
||||
<type>dropdown</type>
|
||||
</field>
|
||||
<field>
|
||||
<id>httpserver.servername</id>
|
||||
<label>Server Name</label>
|
||||
|
|
@ -34,6 +58,20 @@
|
|||
<label>File System Root</label>
|
||||
<type>text</type>
|
||||
</field>
|
||||
<field>
|
||||
<id>httpserver.max_body_size</id>
|
||||
<label>Maximum Body Size</label>
|
||||
<type>text</type>
|
||||
<advanced>true</advanced>
|
||||
<help>If the request is larger, it will be rejectet with error 413 (Request Entity Too Large). For example, you can enter 200m.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>httpserver.body_buffer_size</id>
|
||||
<label>Body Buffer Size</label>
|
||||
<type>text</type>
|
||||
<advanced>true</advanced>
|
||||
<help>If the request exceeds this size, it will be written to disk. Enter a number and a unit like 1m.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>httpserver.certificate</id>
|
||||
<label>TLS Certificate</label>
|
||||
|
|
@ -79,6 +117,28 @@
|
|||
<advanced>true</advanced>
|
||||
<help>Blocks files like .htaccess files or other files not intended for the public.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>httpserver.disable_bot_protection</id>
|
||||
<label>Disable Bot Protection</label>
|
||||
<type>checkbox</type>
|
||||
<advanced>true</advanced>
|
||||
<help>Blocks the request when a possibly bad bot is detected and adds the originating IP to the managed firewall alias for permanent blocking.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>httpserver.ip_acl</id>
|
||||
<label>IP ACL</label>
|
||||
<type>dropdown</type>
|
||||
<style>selectpicker</style>
|
||||
<help>If you select an IP ACL, the client can only access this service if it fulfills this requirement.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>httpserver.satisfy</id>
|
||||
<label>Satisfy</label>
|
||||
<type>dropdown</type>
|
||||
<advanced>true</advanced>
|
||||
<style>selectpicker</style>
|
||||
<help>All: All access restrictions must be fulfilled; Any: Any of the access restrictions must be fulfilled.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>httpserver.naxsi_extensive_log</id>
|
||||
<label>Extensive Naxsi Log</label>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
<form>
|
||||
<field>
|
||||
<id>ipacl.description</id>
|
||||
<label>Description</label>
|
||||
<type>text</type>
|
||||
<help>Enter a short description like a name for this ACL. It will be shown in the drop downs.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>ipacl.data</id>
|
||||
<style>json-data</style>
|
||||
<label>ACL Entries</label>
|
||||
<type>hidden</type>
|
||||
</field>
|
||||
<field>
|
||||
<id>ipacl.default_action</id>
|
||||
<label>Default Action</label>
|
||||
<type>dropdown</type>
|
||||
</field>
|
||||
</form>
|
||||
|
|
@ -131,10 +131,26 @@
|
|||
<type>text</type>
|
||||
<help>Enter the file system root from which the files are served.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>location.max_body_size</id>
|
||||
<label>Maximum Body Size</label>
|
||||
<type>text</type>
|
||||
<advanced>true</advanced>
|
||||
<help>If the request is larger, it will be rejectet with error 413 (Request Entity Too Large). For example, you can enter 200m.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>location.body_buffer_size</id>
|
||||
<label>Body Buffer Size</label>
|
||||
<type>text</type>
|
||||
<advanced>true</advanced>
|
||||
<help>If the request exceeds this size, it will be written to disk. Enter a number and a unit like 1m.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>location.index</id>
|
||||
<label>Index File</label>
|
||||
<type>select_multiple</type>
|
||||
<allownew>true</allownew>
|
||||
<style>tokenize</style>
|
||||
<help>Enter a list of file extensions, which are served instead of a directory. It is common to use index.html or index.php here.</help>
|
||||
</field>
|
||||
<field>
|
||||
|
|
@ -161,6 +177,21 @@
|
|||
<type>checkbox</type>
|
||||
<help>Send an authentication request to the OPNsense backend for advanced access control.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>location.ip_acl</id>
|
||||
<label>IP ACL</label>
|
||||
<type>dropdown</type>
|
||||
<style>selectpicker</style>
|
||||
<help>If you select an IP ACL, the client can only access this service if it fulfills this requirement.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>location.satisfy</id>
|
||||
<label>Satisfy</label>
|
||||
<type>dropdown</type>
|
||||
<style>selectpicker</style>
|
||||
<advanced>true</advanced>
|
||||
<help>All: All access restrictions must be fulfilled; Any: Any of the access restrictions must be fulfilled.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>location.force_https</id>
|
||||
<label>Force HTTPS</label>
|
||||
|
|
@ -192,11 +223,37 @@
|
|||
<advanced>true</advanced>
|
||||
<help>If you enable the honeypot, all requests to this location will go to a special temporary log which will be used to block the IP. This is dangerous because you may accidentally block legitimate users or search engines. The result is available as a special alias in the firewall section. For example you can trigger on locations of Wordpress for phpMyAdmin if you are not using it.</help>
|
||||
</field>
|
||||
<field>
|
||||
<type>header</type>
|
||||
<label>Advanced Proxy Options</label>
|
||||
<advanced>true</advanced>
|
||||
</field>
|
||||
<field>
|
||||
<id>location.websocket</id>
|
||||
<label>WebSocket</label>
|
||||
<label>WebSocket Support</label>
|
||||
<type>checkbox</type>
|
||||
<advanced>true</advanced>
|
||||
<help>If you enable the WebSocket Support option, nginx will pass the upgrade header to the backed server.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>location.proxy_buffering</id>
|
||||
<label>Response Buffering</label>
|
||||
<type>checkbox</type>
|
||||
<advanced>true</advanced>
|
||||
<help>If you enable the Response Buffering option, nginx will buffer response from the backed server.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>location.proxy_request_buffering</id>
|
||||
<label>Request Buffering</label>
|
||||
<type>checkbox</type>
|
||||
<advanced>true</advanced>
|
||||
<help>If you enable the WebSocket option, nginx will pass the upgrade header to the backed server.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>location.proxy_ignore_client_abort</id>
|
||||
<label>Ignore Client Abort</label>
|
||||
<type>checkbox</type>
|
||||
<advanced>true</advanced>
|
||||
<help>If you enable this option, nginx will not terminate the connection to the backend server if the client connection is terminated.</help>
|
||||
</field>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
<form>
|
||||
<field>
|
||||
<id>snihostname.description</id>
|
||||
<label>Short description (to display)</label>
|
||||
<type>text</type>
|
||||
<help>Enter a short description like a name for this redirect.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>snihostname.data</id>
|
||||
<style>json-data</style>
|
||||
<label>Hostname Upstream Map</label>
|
||||
<type>hidden</type>
|
||||
</field>
|
||||
</form>
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
<form>
|
||||
<field>
|
||||
<id>streamserver.listen_port</id>
|
||||
<label>Listen Port</label>
|
||||
<type>text</type>
|
||||
</field>
|
||||
<field>
|
||||
<id>streamserver.udp</id>
|
||||
<label>UDP Port</label>
|
||||
<type>checkbox</type>
|
||||
</field>
|
||||
<field>
|
||||
<id>streamserver.proxy_protocol</id>
|
||||
<label>PROXY Protocol</label>
|
||||
<type>checkbox</type>
|
||||
<help>If you enable the proxy protocol, a downstream proxy can send the client IP and port before the real traffic is set.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>httpserver.trusted_proxies</id>
|
||||
<label>Trusted Proxies</label>
|
||||
<allownew>true</allownew>
|
||||
<style>tokenize</style>
|
||||
<type>select_multiple</type>
|
||||
<advanced>true</advanced>
|
||||
<help>Enter a list of IP addresses or CIDR networks which are allowed to override the source IP address using the specified header.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>streamserver.certificate</id>
|
||||
<label>TLS Certificate</label>
|
||||
<type>dropdown</type>
|
||||
</field>
|
||||
<field>
|
||||
<id>streamserver.ca</id>
|
||||
<label>CA Certificate</label>
|
||||
<type>dropdown</type>
|
||||
</field>
|
||||
<field>
|
||||
<id>streamserver.verify_client</id>
|
||||
<label>Verify Client Certificate</label>
|
||||
<type>dropdown</type>
|
||||
<advanced>true</advanced>
|
||||
<help><![CDATA[<ul><li>On: the certificate is requested and validated. Use this option to protect a service with TLS authentication.</li><li>Off: The certificate is not requested. Choose this option for a normal website.</li><li>Optional: The certificate is requested and validated if existing. Choose this option for websites, with TLS login support or mixed TLS protected API and web content.</li><li>Optional, don't verify: Do accept the certificate and let the application choose what to do. Choose this option, for the same reasons as optional but in this case, the request is passed to the backend without rejecting untrusted certificates.</li></ul>]]></help>
|
||||
</field>
|
||||
<field>
|
||||
<id>streamserver.access_log_format</id>
|
||||
<label>Access Log Format</label>
|
||||
<type>dropdown</type>
|
||||
</field>
|
||||
<field>
|
||||
<id>streamserver.route_field</id>
|
||||
<label>Route With</label>
|
||||
<type>dropdown</type>
|
||||
<style>selectpicker</style>
|
||||
</field>
|
||||
<field>
|
||||
<id>streamserver.upstream</id>
|
||||
<label>Upstream Servers</label>
|
||||
<type>dropdown</type>
|
||||
<style>selectpicker</style>
|
||||
<help>Select an upstream to proxy to.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>streamserver.sni_upstream_map</id>
|
||||
<label>SNI Upstream Mapping</label>
|
||||
<type>dropdown</type>
|
||||
<style>selectpicker</style>
|
||||
<help>Select an upstream map to choose the host based on the name given by the client.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>streamserver.ip_acl</id>
|
||||
<label>IP ACL</label>
|
||||
<type>dropdown</type>
|
||||
<style>selectpicker</style>
|
||||
<help>If you select an IP ACL, the client can only access this service if it fulfills this requirement.</help>
|
||||
</field>
|
||||
</form>
|
||||
|
|
@ -10,6 +10,13 @@
|
|||
<style>selectpicker</style>
|
||||
<type>select_multiple</type>
|
||||
</field>
|
||||
<field>
|
||||
<id>upstream.proxy_protocol</id>
|
||||
<label>PROXY Protocol</label>
|
||||
<type>checkbox</type>
|
||||
<advanced>true</advanced>
|
||||
<help>If you enable the proxy protocol, an upstream proxy or server will get the client IP and the server port before the real traffic is sent.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>upstream.tls_enable</id>
|
||||
<label>Enable TLS (HTTPS)</label>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
<?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.
|
||||
*/
|
||||
|
||||
namespace OPNsense\Nginx;
|
||||
|
||||
class StreamAccessLogLine
|
||||
{
|
||||
public $remote_ip;
|
||||
public $time;
|
||||
public $status;
|
||||
public $bytes_sent;
|
||||
public $bytes_received;
|
||||
public $session_time;
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
<?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.
|
||||
*/
|
||||
|
||||
namespace OPNsense\Nginx;
|
||||
|
||||
class StreamAccessLogParser
|
||||
{
|
||||
private $file_name;
|
||||
private $lines;
|
||||
private $result;
|
||||
|
||||
private const LogLineRegex = '/(\S+) \[([\d\sa-z\:\-\/\+]+)\] (\S+?) (\d+) (\d+) (\d+) (\d+(?:\.\d+)?)/i';
|
||||
|
||||
function __construct($file_name)
|
||||
{
|
||||
$this->file_name = $file_name;
|
||||
$this->lines = file($this->file_name);
|
||||
$this->result = array_map([$this, 'parse_line'], $this->lines);
|
||||
}
|
||||
private function parse_line($line)
|
||||
{
|
||||
$container = new StreamAccessLogLine();
|
||||
if (preg_match(self::LogLineRegex, $line, $data)) {
|
||||
$container->remote_ip = $data[1];
|
||||
$container->time = $data[2];
|
||||
$container->status = $data[3];
|
||||
$container->bytes_sent = $data[4];
|
||||
$container->bytes_received = $data[5];
|
||||
$container->session_time = $data[6];
|
||||
}
|
||||
return $container;
|
||||
}
|
||||
|
||||
public function get_result()
|
||||
{
|
||||
return $this->result;
|
||||
}
|
||||
}
|
||||
|
|
@ -31,4 +31,35 @@ use OPNsense\Base\BaseModel;
|
|||
|
||||
class Nginx extends BaseModel
|
||||
{
|
||||
/**
|
||||
* @param $uuid string UUID of sni_hostname_upstream_map
|
||||
* @return array list of UUIDs
|
||||
*/
|
||||
function find_sni_hostname_upstream_map_entry_uuids($uuid)
|
||||
{
|
||||
return $this->find_x_uuids($uuid, 'sni_hostname_upstream_map.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $uuid string UUID of sni_hostname_upstream_map
|
||||
* @return array list of UUIDs
|
||||
*/
|
||||
function find_ip_acl_uuids($uuid)
|
||||
{
|
||||
return $this->find_x_uuids($uuid, 'ip_acl.');
|
||||
}
|
||||
|
||||
private function find_x_uuids($uuid, $prefix)
|
||||
{
|
||||
$tmp = $this->getNodeByReference($prefix . $uuid);
|
||||
if ($tmp == null) {
|
||||
return [];
|
||||
}
|
||||
$tmp = (string)$tmp->data;
|
||||
if (empty($tmp)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return explode(',', $tmp);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<model>
|
||||
<mount>//OPNsense/Nginx</mount>
|
||||
<version>1.1.2</version>
|
||||
<version>1.5.0</version>
|
||||
<description>nginx web server, reverse proxy and waf</description>
|
||||
<items>
|
||||
<general>
|
||||
|
|
@ -74,6 +74,10 @@
|
|||
<Required>Y</Required>
|
||||
<multiple>Y</multiple>
|
||||
</serverentries>
|
||||
<proxy_protocol type="BooleanField">
|
||||
<default>0</default>
|
||||
<Required>Y</Required>
|
||||
</proxy_protocol>
|
||||
<store type="BooleanField">
|
||||
<Required>Y</Required>
|
||||
<default>0</default>
|
||||
|
|
@ -300,9 +304,9 @@
|
|||
<display>name</display>
|
||||
</template>
|
||||
</Model>
|
||||
<ValidationMessage>Selected server not found</ValidationMessage>
|
||||
<ValidationMessage>Selected user file not found</ValidationMessage>
|
||||
<Required>N</Required>
|
||||
<multiple>Y</multiple>
|
||||
<multiple>N</multiple>
|
||||
</authbasicuserfile>
|
||||
<advanced_acl type="BooleanField">
|
||||
<default>0</default>
|
||||
|
|
@ -330,6 +334,16 @@
|
|||
<Required>N</Required>
|
||||
<multiple>Y</multiple>
|
||||
</limit_request_connections>
|
||||
<max_body_size type="TextField">
|
||||
<Required>N</Required>
|
||||
<mask>/^\d+[kmg]$/i</mask>
|
||||
<ValidationMessage>Enter a number followed by k, m or g.</ValidationMessage>
|
||||
</max_body_size>
|
||||
<body_buffer_size type="TextField">
|
||||
<Required>N</Required>
|
||||
<mask>/^\d+[kmg]$/i</mask>
|
||||
<ValidationMessage>Enter a number followed by k, m or g.</ValidationMessage>
|
||||
</body_buffer_size>
|
||||
<honeypot type="BooleanField">
|
||||
<Required>Y</Required>
|
||||
<default>0</default>
|
||||
|
|
@ -338,10 +352,41 @@
|
|||
<Required>Y</Required>
|
||||
<default>0</default>
|
||||
</websocket>
|
||||
<proxy_ignore_client_abort type="BooleanField">
|
||||
<Required>Y</Required>
|
||||
<default>0</default>
|
||||
</proxy_ignore_client_abort>
|
||||
<proxy_request_buffering type="BooleanField">
|
||||
<Required>Y</Required>
|
||||
<default>1</default>
|
||||
</proxy_request_buffering>
|
||||
<proxy_buffering type="BooleanField">
|
||||
<Required>Y</Required>
|
||||
<default>1</default>
|
||||
</proxy_buffering>
|
||||
<http2_push_preload type="BooleanField">
|
||||
<Required>Y</Required>
|
||||
<default>0</default>
|
||||
</http2_push_preload>
|
||||
<ip_acl type="ModelRelationField">
|
||||
<Model>
|
||||
<template>
|
||||
<source>OPNsense.Nginx.Nginx</source>
|
||||
<items>ip_acl</items>
|
||||
<display>description</display>
|
||||
</template>
|
||||
</Model>
|
||||
<ValidationMessage>Selected ACL not found</ValidationMessage>
|
||||
<Required>N</Required>
|
||||
<multiple>N</multiple>
|
||||
</ip_acl>
|
||||
<satisfy type="OptionField">
|
||||
<OptionValues>
|
||||
<any>Any</any>
|
||||
<all>All</all>
|
||||
</OptionValues>
|
||||
<Required>N</Required>
|
||||
</satisfy>
|
||||
</location>
|
||||
|
||||
<custom_policy type="ArrayField">
|
||||
|
|
@ -398,8 +443,16 @@
|
|||
<Required>Y</Required>
|
||||
</ruletype>
|
||||
<message type="TextField">
|
||||
<Required>Y</Required>
|
||||
<Required>N</Required>
|
||||
<pattern>/^[^"]+$/</pattern>
|
||||
<Constraints>
|
||||
<check001>
|
||||
<ValidationMessage>This field must be set.</ValidationMessage>
|
||||
<type>SetIfConstraint</type>
|
||||
<field>match_type</field>
|
||||
<check>id</check>
|
||||
</check001>
|
||||
</Constraints>
|
||||
</message>
|
||||
<identifier type="IntegerField">
|
||||
<Required>Y</Required>
|
||||
|
|
@ -414,8 +467,16 @@
|
|||
<pattern>/^[^"]+$/</pattern>
|
||||
</dollar_url>
|
||||
<match_value type="TextField">
|
||||
<Required>Y</Required>
|
||||
<Required>N</Required>
|
||||
<pattern>/^[^"]+$/</pattern>
|
||||
<Constraints>
|
||||
<check001>
|
||||
<ValidationMessage>This field must be set.</ValidationMessage>
|
||||
<type>SetIfConstraint</type>
|
||||
<field>match_type</field>
|
||||
<check>id</check>
|
||||
</check001>
|
||||
</Constraints>
|
||||
</match_value>
|
||||
<match_type type="OptionField">
|
||||
<Required>Y</Required>
|
||||
|
|
@ -429,8 +490,16 @@
|
|||
<Required>Y</Required>
|
||||
</negate>
|
||||
<score type="IntegerField">
|
||||
<Required>Y</Required>
|
||||
<Required>N</Required>
|
||||
<default>8</default>
|
||||
<Constraints>
|
||||
<check001>
|
||||
<ValidationMessage>This field must be set.</ValidationMessage>
|
||||
<type>SetIfConstraint</type>
|
||||
<field>match_type</field>
|
||||
<check>id</check>
|
||||
</check001>
|
||||
</Constraints>
|
||||
</score>
|
||||
<regex type="BooleanField">
|
||||
<Required>Y</Required>
|
||||
|
|
@ -473,6 +542,23 @@
|
|||
<Required>N</Required>
|
||||
<default>443</default>
|
||||
</listen_https_port>
|
||||
<proxy_protocol type="BooleanField">
|
||||
<default>0</default>
|
||||
<Required>Y</Required>
|
||||
</proxy_protocol>
|
||||
<trusted_proxies type="CSVListField">
|
||||
<Required>N</Required>
|
||||
<mask>/^((?:\d+\.){3,3}\d+|[a-f0-9\:]+)(?:\/\d+)?(,?(?:(?:(\d+\.){3,3}\d+|[a-f0-9\:]+)(?:\/\d+)?))*$/i</mask>
|
||||
<multiple>Y</multiple>
|
||||
</trusted_proxies>
|
||||
<real_ip_source type="OptionField">
|
||||
<OptionValues>
|
||||
<X-Real-IP>X-Real-IP (default)</X-Real-IP>
|
||||
<X-Forwarded-For>X-Forwarded-For</X-Forwarded-For>
|
||||
<proxy_protocol>PROXY Protocol</proxy_protocol>
|
||||
</OptionValues>
|
||||
<Required>N</Required>
|
||||
</real_ip_source>
|
||||
<locations type="ModelRelationField">
|
||||
<Model>
|
||||
<template>
|
||||
|
|
@ -546,6 +632,10 @@
|
|||
<default>0</default>
|
||||
<Required>Y</Required>
|
||||
</block_nonpublic_data>
|
||||
<disable_bot_protection type="BooleanField">
|
||||
<default>0</default>
|
||||
<Required>Y</Required>
|
||||
</disable_bot_protection>
|
||||
<naxsi_extensive_log type="BooleanField">
|
||||
<default>0</default>
|
||||
<Required>Y</Required>
|
||||
|
|
@ -578,8 +668,226 @@
|
|||
<Required>N</Required>
|
||||
<multiple>Y</multiple>
|
||||
</limit_request_connections>
|
||||
<max_body_size type="TextField">
|
||||
<Required>N</Required>
|
||||
<mask>/^\d+[kmg]$/i</mask>
|
||||
<ValidationMessage>Enter a number followed by k, m or g.</ValidationMessage>
|
||||
</max_body_size>
|
||||
<body_buffer_size type="TextField">
|
||||
<Required>N</Required>
|
||||
<mask>/^\d+[kmg]$/i</mask>
|
||||
<ValidationMessage>Enter a number followed by k, m or g.</ValidationMessage>
|
||||
</body_buffer_size>
|
||||
<ip_acl type="ModelRelationField">
|
||||
<Model>
|
||||
<template>
|
||||
<source>OPNsense.Nginx.Nginx</source>
|
||||
<items>ip_acl</items>
|
||||
<display>description</display>
|
||||
</template>
|
||||
</Model>
|
||||
<ValidationMessage>Selected ACL not found</ValidationMessage>
|
||||
<Required>N</Required>
|
||||
<multiple>N</multiple>
|
||||
</ip_acl>
|
||||
<satisfy type="OptionField">
|
||||
<OptionValues>
|
||||
<any>Any</any>
|
||||
<all>All</all>
|
||||
</OptionValues>
|
||||
<Required>N</Required>
|
||||
</satisfy>
|
||||
</http_server>
|
||||
|
||||
<stream_server type="ArrayField">
|
||||
<listen_port type="PortField">
|
||||
<Required>N</Required>
|
||||
<default>80</default>
|
||||
<Constraints>
|
||||
<check001>
|
||||
<ValidationMessage>You can only use one server at this port.</ValidationMessage>
|
||||
<type>UniqueConstraint</type>
|
||||
</check001>
|
||||
</Constraints>
|
||||
</listen_port>
|
||||
<udp type="BooleanField">
|
||||
<Required>Y</Required>
|
||||
<default>0</default>
|
||||
</udp>
|
||||
<trusted_proxies type="CSVListField">
|
||||
<Required>N</Required>
|
||||
<mask>/^((?:\d+\.){3,3}\d+|[a-f0-9\:]+)(?:\/\d+)?(,?(?:(?:(\d+\.){3,3}\d+|[a-f0-9\:]+)(?:\/\d+)?))*$/i</mask>
|
||||
<multiple>Y</multiple>
|
||||
</trusted_proxies>
|
||||
<proxy_protocol type="BooleanField">
|
||||
<default>0</default>
|
||||
<Required>Y</Required>
|
||||
</proxy_protocol>
|
||||
<certificate type="CertificateField">
|
||||
<Type>cert</Type>
|
||||
<Required>N</Required>
|
||||
</certificate>
|
||||
<ca type="CertificateField">
|
||||
<Type>ca</Type>
|
||||
<Required>N</Required>
|
||||
</ca>
|
||||
<verify_client type="OptionField">
|
||||
<default>Off</default>
|
||||
<OptionValues>
|
||||
<off>Off</off>
|
||||
<on>On</on>
|
||||
<optional>Optional</optional>
|
||||
<optional_no_ca>Optional, don't verify</optional_no_ca>
|
||||
</OptionValues>
|
||||
<Required>Y</Required>
|
||||
</verify_client>
|
||||
<access_log_format type="OptionField">
|
||||
<default>main</default>
|
||||
<OptionValues>
|
||||
<main>Default</main>
|
||||
<anonymized>Anonymized</anonymized>
|
||||
<disabled>Disabled</disabled>
|
||||
</OptionValues>
|
||||
<Required>Y</Required>
|
||||
</access_log_format>
|
||||
<route_field type="OptionField">
|
||||
<default>upstream</default>
|
||||
<OptionValues>
|
||||
<upstream>Upstream</upstream>
|
||||
<sni_upstream_map>SNI Upstream Mapping</sni_upstream_map>
|
||||
</OptionValues>
|
||||
<Required>Y</Required>
|
||||
</route_field>
|
||||
<upstream type="ModelRelationField">
|
||||
<Model>
|
||||
<template>
|
||||
<source>OPNsense.Nginx.Nginx</source>
|
||||
<items>upstream</items>
|
||||
<display>description</display>
|
||||
</template>
|
||||
</Model>
|
||||
<ValidationMessage>Selected upstream not found</ValidationMessage>
|
||||
<Required>N</Required>
|
||||
<multiple>N</multiple>
|
||||
<Constraints>
|
||||
<check001>
|
||||
<ValidationMessage>This field must be set.</ValidationMessage>
|
||||
<type>SetIfConstraint</type>
|
||||
<field>route_field</field>
|
||||
<check>upstream</check>
|
||||
</check001>
|
||||
</Constraints>
|
||||
</upstream>
|
||||
<sni_upstream_map type="ModelRelationField">
|
||||
<Model>
|
||||
<template>
|
||||
<source>OPNsense.Nginx.Nginx</source>
|
||||
<items>sni_hostname_upstream_map</items>
|
||||
<display>description</display>
|
||||
</template>
|
||||
</Model>
|
||||
<ValidationMessage>Selected upstream not found</ValidationMessage>
|
||||
<Required>N</Required>
|
||||
<multiple>N</multiple>
|
||||
<Constraints>
|
||||
<check001>
|
||||
<ValidationMessage>This field must be set.</ValidationMessage>
|
||||
<type>SetIfConstraint</type>
|
||||
<field>route_field</field>
|
||||
<check>sni_upstream_map</check>
|
||||
</check001>
|
||||
</Constraints>
|
||||
</sni_upstream_map>
|
||||
<ip_acl type="ModelRelationField">
|
||||
<Model>
|
||||
<template>
|
||||
<source>OPNsense.Nginx.Nginx</source>
|
||||
<items>ip_acl</items>
|
||||
<display>description</display>
|
||||
</template>
|
||||
</Model>
|
||||
<ValidationMessage>Selected ACL not found</ValidationMessage>
|
||||
<Required>N</Required>
|
||||
<multiple>N</multiple>
|
||||
</ip_acl>
|
||||
</stream_server>
|
||||
|
||||
<sni_hostname_upstream_map type="ArrayField">
|
||||
<description type="TextField">
|
||||
<Required>Y</Required>
|
||||
</description>
|
||||
<data type="TextField">
|
||||
<!-- sorry, the model relation field is broken here
|
||||
<Model>
|
||||
<template>
|
||||
<source>OPNsense.Nginx.Nginx</source>
|
||||
<items>sni_hostname_upstream_map_items</items>
|
||||
<display>hostname</display>
|
||||
</template>
|
||||
</Model>
|
||||
<multiple>Y</multiple>
|
||||
-->
|
||||
<Required>Y</Required>
|
||||
</data>
|
||||
</sni_hostname_upstream_map>
|
||||
|
||||
<sni_hostname_upstream_map_item type="ArrayField">
|
||||
<hostname type="HostnameField">
|
||||
<Required>Y</Required>
|
||||
</hostname>
|
||||
<upstream type="ModelRelationField">
|
||||
<Model>
|
||||
<template>
|
||||
<source>OPNsense.Nginx.Nginx</source>
|
||||
<items>upstream</items>
|
||||
<display>description</display>
|
||||
</template>
|
||||
</Model>
|
||||
<Required>Y</Required>
|
||||
<multiple>N</multiple>
|
||||
</upstream>
|
||||
</sni_hostname_upstream_map_item>
|
||||
|
||||
<ip_acl type="ArrayField">
|
||||
<description type="TextField">
|
||||
<Required>Y</Required>
|
||||
</description>
|
||||
<data type="TextField">
|
||||
<!-- sorry, the model relation field is broken here
|
||||
<Model>
|
||||
<template>
|
||||
<source>OPNsense.Nginx.Nginx</source>
|
||||
<items>ip_acl_item</items>
|
||||
<display>description</display>
|
||||
</template>
|
||||
</Model>
|
||||
<multiple>Y</multiple>
|
||||
-->
|
||||
<Required>Y</Required>
|
||||
</data>
|
||||
<default_action type="OptionField">
|
||||
<OptionValues>
|
||||
<deny>Deny Access</deny>
|
||||
<allow>Allow Access</allow>
|
||||
</OptionValues>
|
||||
<Required>N</Required>
|
||||
</default_action>
|
||||
</ip_acl>
|
||||
|
||||
<ip_acl_item type="ArrayField">
|
||||
<network type="NetworkField">
|
||||
<Required>Y</Required>
|
||||
</network>
|
||||
<action type="OptionField">
|
||||
<default>deny</default>
|
||||
<OptionValues>
|
||||
<deny>Deny Access</deny>
|
||||
<allow>Allow Access</allow>
|
||||
</OptionValues>
|
||||
<Required>Y</Required>
|
||||
</action>
|
||||
</ip_acl_item>
|
||||
|
||||
<http_rewrite type="ArrayField">
|
||||
<description type="TextField">
|
||||
<Required>Y</Required>
|
||||
|
|
@ -997,6 +1305,7 @@
|
|||
<Required>Y</Required>
|
||||
</description>
|
||||
</limit_request_connection>
|
||||
|
||||
<ban type="ArrayField">
|
||||
<ip type="NetworkField">
|
||||
<Required>Y</Required>
|
||||
|
|
@ -1006,6 +1315,7 @@
|
|||
<MinimumValue>0</MinimumValue>
|
||||
</time>
|
||||
</ban>
|
||||
|
||||
<cache_path type="ArrayField">
|
||||
<path type="TextField">
|
||||
<Required>Y</Required>
|
||||
|
|
|
|||
|
|
@ -26,117 +26,63 @@
|
|||
#}
|
||||
|
||||
<script>
|
||||
$( document ).ready(function() {
|
||||
|
||||
let data_get_map = {'frm_nginx':'/api/nginx/settings/get'};
|
||||
|
||||
// load initial data
|
||||
mapDataToFormUI(data_get_map).done(function(){
|
||||
formatTokenizersUI();
|
||||
$('select[data-allownew="false"]').selectpicker('refresh')
|
||||
updateServiceControlUI('nginx');
|
||||
});
|
||||
|
||||
// update history on tab state and implement navigation
|
||||
if(window.location.hash !== "") {
|
||||
$('a[href="' + window.location.hash + '"]').click()
|
||||
}
|
||||
$('.nav-tabs a').on('shown.bs.tab', function (e) {
|
||||
history.pushState(null, null, e.target.hash);
|
||||
});
|
||||
|
||||
$('.reload_btn').click(function() {
|
||||
$(".reloadAct_progress").addClass("fa-spin");
|
||||
ajaxCall(url="/api/nginx/service/reconfigure", sendData={}, callback=function(data,status) {
|
||||
$(".reloadAct_progress").removeClass("fa-spin");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// form save event handlers for all defined forms
|
||||
$('[id*="save_"]').each(function(){
|
||||
$(this).click(function() {
|
||||
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
|
||||
$("#"+frm_id+"_progress").addClass("fa fa-spinner fa-pulse");
|
||||
|
||||
ajaxCall(url="/api/nginx/service/reconfigure", sendData={}, callback=function(data,status){
|
||||
// when done, disable progress animation.
|
||||
$("#"+frm_id+"_progress").removeClass("fa fa-spinner fa-pulse");
|
||||
|
||||
if (data !== undefined && (status !== "success" || data['status'] !== 'ok')) {
|
||||
// fix error handling
|
||||
BootstrapDialog.show({
|
||||
type:BootstrapDialog.TYPE_WARNING,
|
||||
title: frm_title,
|
||||
message: JSON.stringify(data),
|
||||
draggable: true
|
||||
function bind_naxsi_rule_dl_button() {
|
||||
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');
|
||||
});
|
||||
} else {
|
||||
updateServiceControlUI('nginx');
|
||||
}
|
||||
});
|
||||
}, {
|
||||
label: '{{ lang._('Reject') }}',
|
||||
action: function (dlg) {
|
||||
dlg.close();
|
||||
}
|
||||
}]
|
||||
});
|
||||
});
|
||||
});
|
||||
['upstream',
|
||||
'upstreamserver',
|
||||
'location',
|
||||
'credential',
|
||||
'userlist',
|
||||
'httpserver',
|
||||
'httprewrite',
|
||||
'custompolicy',
|
||||
'security_header',
|
||||
'limit_zone',
|
||||
'cache_path',
|
||||
'limit_request_connection',
|
||||
'naxsirule'].forEach(function(element) {
|
||||
$("#grid-" + element).UIBootgrid(
|
||||
{ 'search':'/api/nginx/settings/search' + element,
|
||||
'get':'/api/nginx/settings/get' + element + '/',
|
||||
'set':'/api/nginx/settings/set' + element + '/',
|
||||
'add':'/api/nginx/settings/add' + element + '/',
|
||||
'del':'/api/nginx/settings/del' + element + '/',
|
||||
'options':{selection:false, multiSelect:false}
|
||||
}
|
||||
);
|
||||
});
|
||||
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();
|
||||
}
|
||||
}]
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
</script>
|
||||
<script src="{{ cache_safe('/ui/js/nginx/lib/lodash.min.js') }}"></script>
|
||||
<script src="{{ cache_safe('/ui/js/nginx/lib/backbone-min.js') }}"></script>
|
||||
<script src="{{ cache_safe('/ui/js/nginx/dist/configuration.min.js') }}"></script>
|
||||
<style>
|
||||
#frm_sni_hostname_mapdlg .col-md-4,
|
||||
#frm_ipacl_dlg .col-md-4 {
|
||||
width: 50%;
|
||||
}
|
||||
#frm_sni_hostname_mapdlg td > input[type="text"],
|
||||
#frm_ipacl_dlg td > input[type="text"] {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
#frm_sni_hostname_mapdlg .col-md-5,
|
||||
#frm_ipacl_dlg .col-md-5 {
|
||||
width: 25%;
|
||||
}
|
||||
#row_snihostname\.data .row div,
|
||||
#row_ipacl\.data .row div {
|
||||
padding: 0;
|
||||
}
|
||||
#sni_hostname_mapdlg .bootstrap-select,
|
||||
#frm_ipacl_dlg .bootstrap-select {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
<ul class="nav nav-tabs" role="tablist" id="maintabs">
|
||||
|
|
@ -160,12 +106,6 @@ $( document ).ready(function() {
|
|||
<li>
|
||||
<a data-toggle="tab" id="subtab_item_nginx-http-userlist" href="#subtab_nginx-http-userlist">{{ lang._('User List')}}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a data-toggle="tab" id="subtab_item_nginx-http-upstream-server" href="#subtab_nginx-http-upstream-server">{{ lang._('Upstream Server')}}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a data-toggle="tab" id="subtab_item_nginx-http-upstream" href="#subtab_nginx-http-upstream">{{ lang._('Upstream')}}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a data-toggle="tab" id="subtab_item_nginx-http-server" href="#subtab_nginx-http-httpserver">{{ lang._('HTTP Server')}}</a>
|
||||
</li>
|
||||
|
|
@ -186,6 +126,44 @@ $( 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-streams-streamserver').click();"
|
||||
class="visible-lg-inline-block visible-md-inline-block visible-xs-inline-block visible-sm-inline-block"
|
||||
style="border-right:0px;"><b>{{ lang._('Data Streams')}}</b></a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li>
|
||||
<a data-toggle="tab" id="subtab_item_nginx-streams-streamserver" href="#subtab_nginx-streams-streamserver">{{ lang._('Stream Servers')}}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a data-toggle="tab" id="subtab_item_nginx-streams-snifwd" href="#subtab_nginx-streams-snifwd">{{ lang._('SNI Based Routing')}}</a>
|
||||
</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-http-upstream-server').click();"
|
||||
class="visible-lg-inline-block visible-md-inline-block visible-xs-inline-block visible-sm-inline-block"
|
||||
style="border-right: 0;"><b>{{ lang._('Upstream')}}</b></a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li>
|
||||
<a data-toggle="tab" id="subtab_item_nginx-http-upstream-server" href="#subtab_nginx-http-upstream-server">{{ lang._('Upstream Server')}}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a data-toggle="tab" id="subtab_item_nginx-http-upstream" href="#subtab_nginx-http-upstream">{{ lang._('Upstream')}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li role="presentation" class="dropdown">
|
||||
<a data-toggle="dropdown"
|
||||
href="#"
|
||||
|
|
@ -203,6 +181,9 @@ $( document ).ready(function() {
|
|||
<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>
|
||||
<li>
|
||||
<a data-toggle="tab" id="subtab_item_nginx-acl-ip" href="#subtab_nginx-acl-ip">{{ lang._('IP ACLs')}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
@ -352,6 +333,29 @@ $( document ).ready(function() {
|
|||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
<div id="subtab_nginx-streams-streamserver" class="tab-pane fade">
|
||||
<table id="grid-streamserver" class="table table-condensed table-hover table-striped table-responsive" data-editDialog="streamserverdlg">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="certificate" data-type="string" data-sortable="true" data-visible="true">{{ lang._('Certificate') }}</th>
|
||||
<th data-column-id="udp" data-type="string" data-sortable="true" data-visible="true">{{ lang._('UDP') }}</th>
|
||||
<th data-column-id="listen_port" data-type="string" data-sortable="true" data-visible="true">{{ lang._('Port') }}</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-http-rewrite" class="tab-pane fade">
|
||||
<table id="grid-httprewrite" class="table table-condensed table-hover table-striped table-responsive" data-editDialog="httprewritedlg">
|
||||
<thead>
|
||||
|
|
@ -528,16 +532,58 @@ $( document ).ready(function() {
|
|||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
<div id="subtab_nginx-streams-snifwd" class="tab-pane fade">
|
||||
<table id="grid-snifwd" class="table table-condensed table-hover table-striped table-responsive" data-editDialog="sni_hostname_mapdlg">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="description" data-type="string" data-sortable="true" data-visible="true">{{ lang._('Description') }}</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-acl-ip" class="tab-pane fade">
|
||||
<table id="grid-ipacl" class="table table-condensed table-hover table-striped table-responsive" data-editDialog="ipacl_dlg">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="description" data-type="string" data-sortable="true" data-visible="true">{{ lang._('Description') }}</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>
|
||||
|
||||
|
||||
|
||||
{{ partial("layout_partials/base_dialog",['fields': upstream,'id':'upstreamdlg', 'label':lang._('Edit Upstream')]) }}
|
||||
{{ partial("layout_partials/base_dialog",['fields': upstream_server,'id':'upstreamserverdlg', 'label':lang._('Edit Upstream')]) }}
|
||||
{{ partial("layout_partials/base_dialog",['fields': location,'id':'locationdlg', 'label':lang._('Edit Location')]) }}
|
||||
{{ partial("layout_partials/base_dialog",['fields': credential,'id':'credentialdlg', 'label':lang._('Edit Credential')]) }}
|
||||
{{ partial("layout_partials/base_dialog",['fields': userlist,'id':'userlistdlg', 'label':lang._('Edit User List')]) }}
|
||||
{{ partial("layout_partials/base_dialog",['fields': httpserver,'id':'httpserverdlg', 'label':lang._('Edit HTTP Server')]) }}
|
||||
{{ partial("layout_partials/base_dialog",['fields': streamserver,'id':'streamserverdlg', 'label':lang._('Edit Stream Server')]) }}
|
||||
{{ partial("layout_partials/base_dialog",['fields': httprewrite,'id':'httprewritedlg', 'label':lang._('Edit URL Rewrite')]) }}
|
||||
{{ 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')]) }}
|
||||
|
|
@ -545,3 +591,5 @@ $( document ).ready(function() {
|
|||
{{ 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')]) }}
|
||||
{{ partial("layout_partials/base_dialog",['fields': cache_path,'id':'cache_pathdlg', 'label':lang._('Edit Cache Path')]) }}
|
||||
{{ partial("layout_partials/base_dialog",['fields': sni_hostname_map,'id':'sni_hostname_mapdlg', 'label':lang._('Edit SNI Hostname Mapping')]) }}
|
||||
{{ partial("layout_partials/base_dialog",['fields': ipacl,'id':'ipacl_dlg', 'label':lang._('Edit IP ACL')]) }}
|
||||
|
|
|
|||
|
|
@ -29,4 +29,4 @@
|
|||
|
||||
<script src="{{ cache_safe('/ui/js/nginx/lib/lodash.min.js') }}"></script>
|
||||
<script src="{{ cache_safe('/ui/js/nginx/lib/backbone-min.js') }}"></script>
|
||||
<script src="{{ cache_safe('/ui/js/nginx/dist/bundle.js') }}"></script>
|
||||
<script src="{{ cache_safe('/ui/js/nginx/dist/logviewer.min.js') }}"></script>
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ $log_lines = $log_parser->get_result();
|
|||
$model = new Alias();
|
||||
|
||||
$blacklist_element = null;
|
||||
foreach ($model->aliases->alias->__items as $alias) {
|
||||
foreach ($model->aliases->alias->iterateItems() as $alias) {
|
||||
if ((string)$alias->name == $autoblock_alias_name) {
|
||||
if ((string)$alias->type != 'external') {
|
||||
nginx_print_error('alias is misconfigured - exiting');
|
||||
|
|
@ -111,7 +111,7 @@ if ($blacklist_element == null) {
|
|||
|
||||
$model = new Nginx();
|
||||
$alias_ips = [];
|
||||
foreach ($model->ban->__items as $entry) {
|
||||
foreach ($model->ban->iterateItems() as $entry) {
|
||||
$alias_ips[] = (string)$entry->ip;
|
||||
}
|
||||
|
||||
|
|
@ -126,22 +126,29 @@ $new_ips = array_unique(
|
|||
}, $log_lines)
|
||||
);
|
||||
|
||||
$change_required = false;
|
||||
|
||||
foreach (array_diff($new_ips, $alias_ips) as $new_ip) {
|
||||
$entry = $model->ban->Add();
|
||||
$entry->ip = $new_ip;
|
||||
$entry->time = time();
|
||||
$change_required = true;
|
||||
}
|
||||
$val_result = $model->performValidation(false);
|
||||
if (count($val_result) !== 0) {
|
||||
print_r($val_result);
|
||||
exit(1);
|
||||
|
||||
if ($change_required) {
|
||||
$val_result = $model->performValidation(false);
|
||||
if (count($val_result) !== 0) {
|
||||
print_r($val_result);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$model->serializeToConfig();
|
||||
Config::getInstance()->save();
|
||||
}
|
||||
$model->serializeToConfig();
|
||||
Config::getInstance()->save();
|
||||
echo '{"status":"saved"}';
|
||||
|
||||
// all ips are used because the others may not be set for some reason
|
||||
foreach ($model->ban->__items as $entry) {
|
||||
foreach ($model->ban->iterateItems() as $entry) {
|
||||
add_to_blocklist($autoblock_alias_name, (string)$entry->ip);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ require_once 'config.inc';
|
|||
use OPNsense\Nginx\Nginx;
|
||||
use OPNsense\Nginx\ErrorLogParser;
|
||||
use OPNsense\Nginx\AccessLogParser;
|
||||
use OPNsense\Nginx\StreamAccessLogParser;
|
||||
|
||||
$log_prefix = '/var/log/nginx/';
|
||||
$log_suffix = '.log';
|
||||
|
|
@ -45,35 +46,72 @@ $mode = $_SERVER['argv'][1];
|
|||
$server = $_SERVER['argv'][2];
|
||||
$nginx = new Nginx();
|
||||
|
||||
if ($data = $nginx->getNodeByReference('http_server.'. $server)) {
|
||||
$server_names = (string)$data->servername;
|
||||
if (empty($server_names)) {
|
||||
die('{"error": "The server entry has no server name"}');
|
||||
}
|
||||
$lines = [];
|
||||
foreach (explode(',', $server_names) as $server_name) {
|
||||
$log_file_name = $log_prefix . basename($server_name) . '.' . $mode . $log_suffix;
|
||||
// this entry has no log file, ignore it
|
||||
if (!file_exists($log_file_name)) {
|
||||
continue;
|
||||
}
|
||||
$logparser = null;
|
||||
switch ($mode) {
|
||||
case 'error':
|
||||
case 'access':
|
||||
if ($data = $nginx->getNodeByReference('http_server.'. $server)) {
|
||||
$server_names = (string)$data->servername;
|
||||
if (empty($server_names)) {
|
||||
die('{"error": "The server entry has no server name"}');
|
||||
}
|
||||
$lines = [];
|
||||
foreach (explode(',', $server_names) as $server_name) {
|
||||
$log_file_name = $log_prefix . basename($server_name) . '.' . $mode . $log_suffix;
|
||||
// this entry has no log file, ignore it
|
||||
if (!file_exists($log_file_name)) {
|
||||
continue;
|
||||
}
|
||||
$logparser = null;
|
||||
|
||||
if ($mode == 'error') {
|
||||
$logparser = new ErrorLogParser($log_file_name);
|
||||
} elseif ($mode == 'access') {
|
||||
$logparser = new AccessLogParser($log_file_name);
|
||||
if ($mode == 'error') {
|
||||
$logparser = new ErrorLogParser($log_file_name);
|
||||
} elseif ($mode == 'access') {
|
||||
$logparser = new AccessLogParser($log_file_name);
|
||||
}
|
||||
// we cannot parse the file - something went wrong
|
||||
if ($logparser == null) {
|
||||
continue;
|
||||
}
|
||||
$lines = array_merge($lines, $logparser->get_result());
|
||||
}
|
||||
if (empty($lines)) {
|
||||
$lines['error'] = 'no lines found';
|
||||
}
|
||||
echo json_encode($lines);
|
||||
} else {
|
||||
die('{"error": "UUID not found"}');
|
||||
}
|
||||
// we cannot parse the file - something went wrong
|
||||
if ($logparser == null) {
|
||||
continue;
|
||||
break;
|
||||
case 'streamerror':
|
||||
case 'streamaccess':
|
||||
if ($data = $nginx->getNodeByReference('stream_server.'. $server)) {
|
||||
$lines = [];
|
||||
$mode = str_replace('stream', '', $mode);
|
||||
$log_file_name = $log_prefix . 'stream_' . $server . '.' . $mode . $log_suffix;
|
||||
// this entry has no log file, ignore it
|
||||
if (!file_exists($log_file_name)) {
|
||||
die('{"error": "file not found"}');
|
||||
}
|
||||
$logparser = null;
|
||||
|
||||
if ($mode == 'error') {
|
||||
$logparser = new ErrorLogParser($log_file_name);
|
||||
} elseif ($mode == 'access') {
|
||||
$logparser = new StreamAccessLogParser($log_file_name);
|
||||
}
|
||||
// we cannot parse the file - something went wrong
|
||||
if ($logparser == null) {
|
||||
continue;
|
||||
}
|
||||
$lines = array_merge($lines, $logparser->get_result());
|
||||
if (empty($lines)) {
|
||||
$lines['error'] = 'no lines found';
|
||||
}
|
||||
echo json_encode($lines);
|
||||
} else {
|
||||
die('{"error": "UUID not found"}');
|
||||
}
|
||||
$lines = array_merge($lines, $logparser->get_result());
|
||||
}
|
||||
if (empty($lines)) {
|
||||
$lines['error'] = 'no lines found';
|
||||
}
|
||||
echo json_encode($lines);
|
||||
} else {
|
||||
die('{"error": "UUID not found"}');
|
||||
break;
|
||||
default:
|
||||
die('{"error": "action (' . $mode . ') not found"}');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,49 +67,92 @@ function find_ca($refid)
|
|||
if (!isset($config['OPNsense']['Nginx'])) {
|
||||
die("nginx is not configured");
|
||||
}
|
||||
$nginx = $config['OPNsense']['Nginx'];
|
||||
if (!isset($nginx['http_server'])) {
|
||||
die("no http servers configured");
|
||||
}
|
||||
if (is_array($nginx['http_server']) && !isset($nginx['http_server']['servername'])) {
|
||||
$http_servers = $nginx['http_server'];
|
||||
} else {
|
||||
$http_servers = array($nginx['http_server']);
|
||||
}
|
||||
@mkdir('/usr/local/etc/nginx/key', 0750, true);
|
||||
@mkdir("/var/db/nginx/auth", 0750, true);
|
||||
foreach ($http_servers as $http_server) {
|
||||
if (!empty($http_server['listen_https_port']) && !empty($http_server['certificate'])) {
|
||||
// try to find the reference
|
||||
$cert = find_cert($http_server['certificate']);
|
||||
if (!isset($cert)) {
|
||||
next;
|
||||
}
|
||||
$chain = [];
|
||||
$ca_chain = ca_chain_array($cert);
|
||||
if (is_array($ca_chain)) {
|
||||
foreach ($ca_chain as $entry) {
|
||||
$chain[] = base64_decode($entry['crt']);
|
||||
$nginx = $config['OPNsense']['Nginx'];
|
||||
if (isset($nginx['http_server'])) {
|
||||
if (is_array($nginx['http_server']) && !isset($nginx['http_server']['servername'])) {
|
||||
$http_servers = $nginx['http_server'];
|
||||
} else {
|
||||
$http_servers = array($nginx['http_server']);
|
||||
}
|
||||
foreach ($http_servers as $http_server) {
|
||||
if (!empty($http_server['listen_https_port']) && !empty($http_server['certificate'])) {
|
||||
// try to find the reference
|
||||
$cert = find_cert($http_server['certificate']);
|
||||
if (!isset($cert)) {
|
||||
next;
|
||||
}
|
||||
$chain = [];
|
||||
$ca_chain = ca_chain_array($cert);
|
||||
if (is_array($ca_chain)) {
|
||||
foreach ($ca_chain as $entry) {
|
||||
$chain[] = base64_decode($entry['crt']);
|
||||
}
|
||||
}
|
||||
$hostname = explode(',', $http_server['servername'])[0];
|
||||
export_pem_file(
|
||||
KEY_DIRECTORY . $hostname . '.pem',
|
||||
$cert['crt'],
|
||||
implode("\n", $chain)
|
||||
);
|
||||
export_pem_file(
|
||||
KEY_DIRECTORY . $hostname . '.key',
|
||||
$cert['prv']
|
||||
);
|
||||
if (!empty($http_server['ca'])) {
|
||||
foreach ($http_server['ca'] as $caref) {
|
||||
$ca = find_ca($caref);
|
||||
if (isset($ca)) {
|
||||
export_pem_file(
|
||||
KEY_DIRECTORY . $hostname . '_ca.pem',
|
||||
$ca['crt']
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$hostname = explode(',', $http_server['servername'])[0];
|
||||
export_pem_file(
|
||||
KEY_DIRECTORY . $hostname . '.pem',
|
||||
$cert['crt'],
|
||||
implode("\n", $chain)
|
||||
);
|
||||
export_pem_file(
|
||||
KEY_DIRECTORY . $hostname . '.key',
|
||||
$cert['prv']
|
||||
);
|
||||
if (!empty($http_server['ca'])) {
|
||||
foreach ($http_server['ca'] as $caref) {
|
||||
$ca = find_ca($caref);
|
||||
if (isset($ca)) {
|
||||
export_pem_file(
|
||||
KEY_DIRECTORY . $hostname . '_ca.pem',
|
||||
$ca['crt']
|
||||
);
|
||||
}
|
||||
}
|
||||
// end http, begin streams
|
||||
if (isset($nginx['stream_server'])) {
|
||||
if (is_array($nginx['stream_server']) && !isset($nginx['stream_server']['servername'])) {
|
||||
$stream_servers = $nginx['stream_server'];
|
||||
} else {
|
||||
$stream_servers = array($nginx['stream_server']);
|
||||
}
|
||||
foreach ($stream_servers as $stream_server) {
|
||||
if (!empty($stream_server['listen_port']) && !empty($stream_server['certificate'])) {
|
||||
// try to find the reference
|
||||
$cert = find_cert($stream_server['certificate']);
|
||||
if (!isset($cert)) {
|
||||
next;
|
||||
}
|
||||
$chain = [];
|
||||
$ca_chain = ca_chain_array($cert);
|
||||
if (is_array($ca_chain)) {
|
||||
foreach ($ca_chain as $entry) {
|
||||
$chain[] = base64_decode($entry['crt']);
|
||||
}
|
||||
}
|
||||
export_pem_file(
|
||||
KEY_DIRECTORY . $stream_server['@attributes']['uuid'] . '.pem',
|
||||
$cert['crt'],
|
||||
implode("\n", $chain)
|
||||
);
|
||||
export_pem_file(
|
||||
KEY_DIRECTORY . $stream_server['@attributes']['uuid'] . '.key',
|
||||
$cert['prv']
|
||||
);
|
||||
if (!empty($stream_server['ca'])) {
|
||||
foreach ($stream_server['ca'] as $caref) {
|
||||
$ca = find_ca($caref);
|
||||
if (isset($ca)) {
|
||||
export_pem_file(
|
||||
KEY_DIRECTORY . $hostname . '_ca.pem',
|
||||
$ca['crt']
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -170,7 +213,7 @@ if (isset($nginx['upstream'])) {
|
|||
|
||||
// export users
|
||||
$nginx = new Nginx();
|
||||
foreach ($nginx->userlist->__items as $user_list) {
|
||||
foreach ($nginx->userlist->iterateItems() as $user_list) {
|
||||
$attributes = $user_list->getAttributes();
|
||||
$uuid = $attributes['uuid'];
|
||||
$file = null;
|
||||
|
|
@ -191,6 +234,6 @@ foreach ($nginx->userlist->__items as $user_list) {
|
|||
}
|
||||
}
|
||||
// create directories for cache
|
||||
foreach ($nginx->cache_path->__items as $cache_path) {
|
||||
foreach ($nginx->cache_path->iterateItems() as $cache_path) {
|
||||
@mkdir((string)$cache_path->path, 0755, true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,3 +5,4 @@ mime.types:/usr/local/etc/nginx/mime.types
|
|||
php_fpm:/etc/rc.conf.d/php_fpm
|
||||
php-www.conf:/usr/local/etc/php-fpm.d/www.conf
|
||||
php-webgui.conf:/usr/local/etc/php-fpm.d/webgui.conf
|
||||
newsyslog.conf:/etc/newsyslog.conf.d/nginx
|
||||
|
|
|
|||
|
|
@ -57,9 +57,11 @@ if cache_path.use_temp_path is defined and cache_path.use_temp_path == '1'
|
|||
{% for server in helpers.toList('OPNsense.Nginx.http_server') %}
|
||||
{% set single_servername = server.servername.split(",")[0] %}
|
||||
server {
|
||||
{% set our_headers = [] %}
|
||||
{% do our_headers.append('X-Powered-By') %}
|
||||
{% if server.listen_http_port is defined %}
|
||||
listen {{ server.listen_http_port }};
|
||||
listen [::]:{{ server.listen_http_port }};
|
||||
listen {{ server.listen_http_port }}{% if server.proxy_protocol is defined and server.proxy_protocol == '1' %} proxy_protocol{% endif %};
|
||||
listen [::]:{{ server.listen_http_port }}{% if server.proxy_protocol is defined and server.proxy_protocol == '1' %} proxy_protocol{% endif %};
|
||||
{% do listen_list.append(server.listen_http_port) %}
|
||||
{% endif %}
|
||||
{% if server.listen_https_port is defined and server.certificate is defined %}
|
||||
|
|
@ -80,14 +82,34 @@ server {
|
|||
ssl_session_tickets off;
|
||||
ssl_prefer_server_ciphers on;
|
||||
add_header Strict-Transport-Security max-age=15768000;
|
||||
{% do our_headers.append('Strict-Transport-Security') %}
|
||||
sendfile {% if server.sendfile is defined and server.sendfile == '1' %}On{% else %}Off{% endif %};
|
||||
{% endif %}
|
||||
server_name {{ server.servername.replace(',', ' ') }};
|
||||
{% if server.real_ip_source is defined and server.real_ip_source != '' %}
|
||||
real_ip_header {{ server.real_ip_source }};
|
||||
{% if server.trusted_proxies is defined and server.trusted_proxies != '' %}
|
||||
{% for trusted_proxy in server.trusted_proxies.split(',') %}
|
||||
set_real_ip_from {{ trusted_proxy }};
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if server.charset is defined %}
|
||||
charset {{ server.charset }};
|
||||
{% endif %}
|
||||
access_log /var/log/nginx/{{ server.servername }}.access.log {{ server.access_log_format }};
|
||||
error_log /var/log/nginx/{{ server.servername }}.error.log;
|
||||
{% if server.root is defined and server.root != '' %}
|
||||
root "{{server.root}}";
|
||||
{% endif %}
|
||||
{% if server.max_body_size is defined %}
|
||||
client_max_body_size {{ server.max_body_size }};
|
||||
{% endif %}
|
||||
{% if server.body_buffer_size is defined %}
|
||||
client_body_buffer_size {{ server.body_buffer_size }};
|
||||
{% endif %}
|
||||
{% if server.satisfy is defined %}
|
||||
satisfy {{ server.satisfy }};
|
||||
{% endif %}
|
||||
#include tls.conf;
|
||||
error_page 404 /opnsense_error_404.html;
|
||||
|
|
@ -130,6 +152,7 @@ server {
|
|||
root /var/etc/acme-client/challenges;
|
||||
}
|
||||
{% endif %}
|
||||
{% if server.disable_bot_protection is not defined or server.disable_bot_protection != '1' %}
|
||||
# block based on User Agents - stuff I have found over the years in my server log
|
||||
if ($http_user_agent ~* Python-urllib|Nmap|python-requests|libwww-perl|MJ12bot|Jorgee|fasthttp|libwww|Telesphoreo|A6-Indexer|ltx71|okhttp|ZmEu|sqlmap|LMAO/2.0|ltx71|zgrab|Ronin/2.0|Hakai/2.0) {
|
||||
return 418;
|
||||
|
|
@ -143,6 +166,11 @@ server {
|
|||
{
|
||||
return 418;
|
||||
}
|
||||
{% endif %}
|
||||
{% if server.ip_acl is defined %}
|
||||
{% set ip_acl = server.ip_acl %}
|
||||
{% include "OPNsense/Nginx/ipacl.conf" %}
|
||||
{% endif %}
|
||||
|
||||
location = /opnsense-report-csp-violation {
|
||||
include fastcgi_params;
|
||||
|
|
@ -200,7 +228,9 @@ server {
|
|||
{% if server.locations is defined %}
|
||||
{% for location_uuid in server.locations.split(',') %}
|
||||
{% set location = helpers.getUUID(location_uuid) %}
|
||||
{% if location.urlpattern is defined %}
|
||||
{% include "OPNsense/Nginx/location.conf" ignore missing with context %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
# IP ACL
|
||||
{% if ip_acl is defined %}
|
||||
{% set ipacl_data = helpers.getUUID(ip_acl) %}
|
||||
{% if ipacl_data is defined %}
|
||||
{% for acl_entry_uuid in ipacl_data.data.split(',') %}
|
||||
{% set acl_entry = helpers.getUUID(acl_entry_uuid) %}
|
||||
{% if acl_entry is defined %}
|
||||
{{ acl_entry.action }} {{ acl_entry.network }};
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if ipacl_data.default_action is defined %}
|
||||
{{ ipacl_data.default_action }} all;
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
|
@ -40,9 +40,22 @@ location {{ location.matchtype }} {{ location.urlpattern }} {
|
|||
return 302 https://$host$request_uri;
|
||||
}
|
||||
{% endif %}
|
||||
{% if location.ip_acl is defined %}
|
||||
{% set ip_acl = server.ip_acl %}
|
||||
{% include "OPNsense/Nginx/ipacl.conf" %}
|
||||
{% endif %}
|
||||
{% if location.root is defined %}
|
||||
root {{ location.root }};
|
||||
{% endif %}
|
||||
{% if location.max_body_size is defined %}
|
||||
client_max_body_size {{ location.max_body_size }};
|
||||
{% endif %}
|
||||
{% if location.body_buffer_size is defined %}
|
||||
client_body_buffer_size {{ location.body_buffer_size }};
|
||||
{% endif %}
|
||||
{% if location.satisfy is defined %}
|
||||
satisfy {{ location.satisfy }};
|
||||
{% endif %}
|
||||
{% if location.index is defined %}
|
||||
index {{ location.index.replace(",", " ") }};
|
||||
{% endif %}
|
||||
|
|
@ -122,6 +135,9 @@ location {{ location.matchtype }} {{ location.urlpattern }} {
|
|||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_ignore_client_abort {% if location.proxy_ignore_client_abort == '1' %}on{% else %}off{% endif %};
|
||||
proxy_request_buffering {% if location.proxy_request_buffering == '1' %}on{% else %}off{% endif %};
|
||||
proxy_buffering {% if location.proxy_buffering == '1' %}on{% else %}off{% endif %};
|
||||
{% if location.path_prefix is defined and location.path_prefix != '' %}
|
||||
proxy_pass http{% if upstream.tls_enable == '1' %}s{% endif %}://upstream{{ location.upstream.replace('-','') }}{{ location.path_prefix }};
|
||||
{% else %}
|
||||
|
|
@ -153,6 +169,9 @@ location {{ location.matchtype }} {{ location.urlpattern }} {
|
|||
proxy_store {% if upstream.store == '1' %}on{% else %}off{% endif %};
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% for our_header in our_headers %}
|
||||
proxy_hide_header {{ our_header }};
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,10 +39,14 @@
|
|||
{{ mz_matches|join('|') }}
|
||||
{%- endmacro %}
|
||||
{% macro naxsi_rule(uuid, rule, ruletype) -%}
|
||||
{% if rule.message is defined and rule.match_value is defined %}
|
||||
{{ ruletype }}{% if rule.negate is defined and rule.negate == '1' %} negative{% endif
|
||||
%} {{ rule.match_type }}:{{ rule.identifier }} "{% if rule.regex == '1' %}rx{% else %}str{% endif
|
||||
%}:{{ rule.match_value }}" "msg:{{ rule.message }}" "mz:{{ naxsi_mzhelper(rule)
|
||||
}}" "s:$policy{{ uuid.replace('-', '') }}:{{ rule.score }}";
|
||||
{% else %}
|
||||
{{ ruletype }} {{ rule.match_type }}:{{ rule.identifier }};
|
||||
{% endif %}
|
||||
{%- endmacro %}
|
||||
|
||||
{% if naxsi_ruletype == 'basic' %}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
# logfilename [owner:group] mode count size when flags [/pid_file] [sig_num]
|
||||
{% if helpers.exists('OPNsense.Nginx') %}
|
||||
/var/log/nginx/*access.log www:www 640 14 * @T00 GZB /var/run/nginx.pid 30
|
||||
/var/log/nginx/*error.log www:www 640 14 * @T00 GZB /var/run/nginx.pid 30
|
||||
{% endif %}
|
||||
|
|
@ -17,10 +17,14 @@ events {
|
|||
http {
|
||||
{% if helpers.exists('OPNsense.Nginx') %}
|
||||
{# include http blocks partial #}
|
||||
{% include "OPNsense/Nginx/http.conf" ignore missing with context %}
|
||||
{% include "OPNsense/Nginx/http.conf" %}
|
||||
{% endif %}
|
||||
}
|
||||
{% if helpers.exists('OPNsense.Nginx') %}
|
||||
stream {
|
||||
{# include streams blocks partial #}
|
||||
{% include "OPNsense/Nginx/streams.conf" %}
|
||||
}
|
||||
# mail {
|
||||
{# include http blocks partial #}
|
||||
{% include "OPNsense/Nginx/mail.conf" ignore missing with context %}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,24 @@
|
|||
{% if security_rule.referrer is defined %}
|
||||
{% do our_headers.append('Referrer-Policy') %}
|
||||
add_header Referrer-Policy "{{ security_rule.referrer }}" always;
|
||||
{% endif %}
|
||||
{% if security_rule.xssprotection is defined %}
|
||||
{% do our_headers.append('X-XSS-Protection') %}
|
||||
add_header X-XSS-Protection "{{ security_rule.xssprotection }}" always;
|
||||
{% endif %}
|
||||
{% if security_rule.content_type_options is defined and security_rule.content_type_options == '1' %}
|
||||
{% do our_headers.append('X-Content-Type-Options') %}
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
{% endif %}
|
||||
{% if security_rule.strict_transport_security_time is defined %}
|
||||
{% do our_headers.append('Strict-Transport-Security') %}
|
||||
add_header Strict-Transport-Security "{{ security_rule.strict_transport_security_time }}{%
|
||||
if security_rule.strict_transport_security_include_subdomains is defined and
|
||||
security_rule.strict_transport_security_include_subdomains == '1' %}; includeSubDomains{% endif %}" always;
|
||||
{% endif %}
|
||||
{% if security_rule.hpkp_keys is defined and security_rule.hpkp_time is defined %}
|
||||
{% do our_headers.append('Public-Key-Pins') %}
|
||||
{% do our_headers.append('Public-Key-Pins-Report-Only') %}
|
||||
add_header Public-Key-Pins{% if security_rule.hpkp_report_only is defined and security_rule.hpkp_report_only == '1'
|
||||
%}-Report-Only{% endif %} "{% for key in security_rule.hpkp_keys.split(',')
|
||||
%}pin-sha256={{ key }}; {% endfor %}max-age={{ security_rule.hpkp_time }}{%
|
||||
|
|
@ -59,6 +65,8 @@
|
|||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% do our_headers.append('Content-Security-Policy') %}
|
||||
{% do our_headers.append('Content-Security-Policy-Report-Only') %}
|
||||
add_header Content-Security-Policy{% if security_rule.csp_report_only %}-Report-Only{% endif %} "{%
|
||||
for key, value in hash_csp.items() %}{{ key }} {{ value|join(' ') }}; {% endfor %}{#
|
||||
#} report-uri /opnsense-report-csp-violation" always;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
# LOG FORMATS
|
||||
log_format main '$remote_addr [$time_local] '
|
||||
'$protocol $status $bytes_sent $bytes_received '
|
||||
'$session_time';
|
||||
log_format anonymized ':: [$time_local] '
|
||||
'$protocol $status $bytes_sent $bytes_received '
|
||||
'$session_time';
|
||||
|
||||
# UPSTREAM SERVERS
|
||||
{% for upstream in helpers.toList('OPNsense.Nginx.upstream') %}
|
||||
upstream upstream{{ upstream['@uuid'].replace('-','') }} {
|
||||
hash $remote_addr consistent;
|
||||
{% 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
|
||||
%}{% if upstream_server.priority is defined %} weight={{ upstream_server.priority }}{% endif
|
||||
%}{% if upstream_server.max_conns is defined %} max_conns={{ upstream_server.max_conns }}{% endif
|
||||
%}{% if upstream_server.max_fails is defined %} max_fails={{ upstream_server.max_fails }}{% endif
|
||||
%}{% if upstream_server.fail_timeout is defined %} fail_timeout={{ upstream_server.fail_timeout }}{% endif
|
||||
%}{% if upstream_server.no_use is defined %} {{ upstream_server.no_use }}{% endif %};
|
||||
{% endfor %}
|
||||
}
|
||||
{% endfor %}
|
||||
|
||||
# upstream maps
|
||||
{% for upstream_map in helpers.toList('OPNsense.Nginx.sni_hostname_upstream_map') %}
|
||||
map $ssl_preread_server_name $hostmap{{ upstream_map['@uuid'].replace('-','') }} {
|
||||
{% for map_entry_uuid in upstream_map.data.split(',') %}
|
||||
{% set map_entry = helpers.getUUID(map_entry_uuid) %}
|
||||
{{ map_entry.hostname }} upstream{{ map_entry.upstream.replace('-','') }};
|
||||
{% endfor %}
|
||||
|
||||
}
|
||||
{% endfor %}
|
||||
|
||||
{% for server in helpers.toList('OPNsense.Nginx.stream_server') %}
|
||||
# servers
|
||||
server {
|
||||
{% set tls_enabled = server.certificate is defined %}
|
||||
{% if server.listen_port is defined %}
|
||||
listen {{ server.listen_port }}{% if server.udp is defined and server.udp == '1' %} udp{% endif %}{% if tls_enabled %} ssl{% endif %}{% if server.proxy_protocol is defined and server.proxy_protocol == '1' %} proxy_protocol{% endif %};
|
||||
listen [::]:{{ server.listen_port }}{% if server.udp is defined and server.udp == '1' %} udp{% endif %}{% if tls_enabled %} ssl{% endif %}{% if server.proxy_protocol is defined and server.proxy_protocol == '1' %} proxy_protocol{% endif %};
|
||||
{% endif %}
|
||||
|
||||
access_log /var/log/nginx/stream_{{ server['@uuid'] }}.access.log main;
|
||||
error_log /var/log/nginx/stream_{{ server['@uuid'] }}.error.log info;
|
||||
|
||||
{% if server.route_field == 'sni_upstream_map' %}
|
||||
ssl_preread on;
|
||||
{% endif %}
|
||||
{% if server.ip_acl is defined %}
|
||||
{% set ip_acl = server.ip_acl %}
|
||||
{% include "OPNsense/Nginx/ipacl.conf" %}
|
||||
{% endif %}
|
||||
{% if server.certificate is defined %}
|
||||
{% if server.ca is defined %}
|
||||
ssl_client_certificate /usr/local/etc/nginx/key/{{ server['@uuid'] }}_ca.pem;
|
||||
ssl_verify_client {{ server.verify_client }};
|
||||
{% endif %}
|
||||
ssl_certificate_key /usr/local/etc/nginx/key/{{ server['@uuid'] }}.key;
|
||||
ssl_certificate /usr/local/etc/nginx/key/{{ server['@uuid'] }}.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_dhparam /usr/local/etc/dh-parameters.4096;
|
||||
ssl_ciphers 'ECDHE-ECDSA-CAMELLIA256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CAMELLIA256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-CAMELLIA128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-CAMELLIA128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-CAMELLIA256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-CAMELLIA256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-CAMELLIA128-SHA256:ECDHE-RSA-AES128-SHA256';
|
||||
ssl_session_timeout 1d;
|
||||
ssl_session_cache shared:sslcache{{ server['@uuid'].replace('-','') }}:50m;
|
||||
ssl_session_tickets off;
|
||||
ssl_prefer_server_ciphers on;
|
||||
{% endif %}
|
||||
|
||||
{% if server.route_field == 'upstream' %}
|
||||
{% if server.upstream is defined %}
|
||||
{% set upstream = helpers.getUUID(server.upstream) %}
|
||||
{% if upstream.tls_enable == '1' %}
|
||||
{% if upstream.tls_client_certificate is defined and upstream.tls_client_certificate != '' %}
|
||||
proxy_ssl_certificate_key /usr/local/etc/nginx/key/{{ upstream.tls_client_certificate }}.key;
|
||||
proxy_ssl_certificate /usr/local/etc/nginx/key/{{ upstream.tls_client_certificate }}.pem;
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
proxy_ssl {% if upstream.tls_enable == '1' %}on{% else %}off{% endif %};
|
||||
proxy_pass upstream{{ server.upstream.replace('-','') }};
|
||||
{% endif %}
|
||||
{% elif server.route_field == 'sni_upstream_map' %}
|
||||
proxy_pass $hostmap{{ server.sni_upstream_map.replace('-','') }};
|
||||
{% endif %}
|
||||
proxy_protocol {% if upstream.proxy_protocol == '1' %}on{% else %}off{% endif %};
|
||||
{% if server.trusted_proxies is defined and server.trusted_proxies != '' %}
|
||||
{% for trusted_proxy in server.trusted_proxies.split(',') %}
|
||||
set_real_ip_from {{ trusted_proxy }};
|
||||
{% endfor %}
|
||||
{% endif%}
|
||||
|
||||
}
|
||||
{% endfor %}
|
||||
File diff suppressed because one or more lines are too long
1
www/nginx/src/opnsense/www/js/nginx/dist/configuration.min.js
vendored
Normal file
1
www/nginx/src/opnsense/www/js/nginx/dist/configuration.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
www/nginx/src/opnsense/www/js/nginx/dist/logviewer.min.js
vendored
Normal file
1
www/nginx/src/opnsense/www/js/nginx/dist/logviewer.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -1,10 +1,18 @@
|
|||
export const defaultEndpoints = new Backbone.Collection([
|
||||
{
|
||||
"name": 'Access Logs',
|
||||
"name": 'HTTP Access Logs',
|
||||
"logType" : 'accesses'
|
||||
},
|
||||
{
|
||||
"name": 'Error Logs',
|
||||
"name": 'HTTP Error Logs',
|
||||
"logType" : 'errors'
|
||||
},
|
||||
{
|
||||
"name": 'Stream Access Logs',
|
||||
"logType" : 'stream_accesses'
|
||||
},
|
||||
{
|
||||
"name": 'Stream Error Logs',
|
||||
"logType" : 'stream_errors'
|
||||
}
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
export default Backbone.View.extend({
|
||||
tagName: 'div',
|
||||
attributes: {'class': 'container-fluid'},
|
||||
child_views: [],
|
||||
createModel: null,
|
||||
upstreamCollection: null,
|
||||
initialize: function (params) {
|
||||
this.dataField = $(params.dataField);
|
||||
this.entryclass = params.entryclass;
|
||||
this.createModel = params.createModel;
|
||||
this.upstreamCollection = params.upstreamCollection;
|
||||
this.listenTo(this.collection, "add remove reset", this.render);
|
||||
this.listenTo(this.collection, "change", this.update);
|
||||
// inject our table holder
|
||||
this.dataField.after(this.$el);
|
||||
},
|
||||
events: {
|
||||
"click .add": "addEntry"
|
||||
},
|
||||
render: function () {
|
||||
// clear table
|
||||
this.child_views.forEach((model) => model.remove());
|
||||
this.$el.html('');
|
||||
this.child_views = [];
|
||||
this.update();
|
||||
this.collection.each((model) => {
|
||||
const childView = new this.entryclass({
|
||||
model: model,
|
||||
collection: this.collection,
|
||||
upstreamCollection: this.upstreamCollection
|
||||
});
|
||||
this.child_views.push(childView);
|
||||
this.$el.append(childView.$el);
|
||||
childView.render();
|
||||
});
|
||||
this.$el.append($(`
|
||||
<div class="row">
|
||||
<button class="btn btn-primary pull-right add">
|
||||
<span class="fa fa-plus"></span>
|
||||
</button>
|
||||
</div>`));
|
||||
},
|
||||
update: function () {
|
||||
this.dataField.data('data', this.collection.toJSON());
|
||||
},
|
||||
addEntry: function (e) {
|
||||
e.preventDefault();
|
||||
this.collection.add(this.createModel());
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
export const KeyValueMapFieldEntryUpstreamMap = Backbone.View.extend({
|
||||
|
||||
tagName: 'div',
|
||||
attributes: {'class': 'row'},
|
||||
events: {
|
||||
'keyup .key': function () {
|
||||
this.model.set('hostname', this.key.value);
|
||||
},
|
||||
'change .value': function () {
|
||||
this.model.set('upstream', this.value.value);
|
||||
},
|
||||
"click .delete" : "deleteEntry"
|
||||
},
|
||||
key: null,
|
||||
value: null,
|
||||
delBtn: null,
|
||||
first: null,
|
||||
second: null,
|
||||
third: null,
|
||||
upstreamCollection: null,
|
||||
initialize: function (params) {
|
||||
this.upstreamCollection = params.upstreamCollection;
|
||||
this.listenTo(this.upstreamCollection, "update reset add remove", this.regenerate_list);
|
||||
this.first = document.createElement('div');
|
||||
this.first.classList.add('col-sm-5');
|
||||
this.key = document.createElement('input');
|
||||
this.first.append(this.key);
|
||||
this.key.type = 'text';
|
||||
this.key.classList.add('key');
|
||||
this.key.value = this.model.get('hostname');
|
||||
|
||||
this.second = document.createElement('div');
|
||||
this.second.classList.add('col-sm-5');
|
||||
this.value = document.createElement('select');
|
||||
this.second.append(this.value);
|
||||
this.value.classList.add('value');
|
||||
this.value.classList.add('form-control');
|
||||
this.value.value = this.model.get('upstream');
|
||||
|
||||
this.third = document.createElement('div');
|
||||
this.third.classList.add('col-sm-2');
|
||||
this.third.style.textAlign = 'right';
|
||||
this.delBtn = document.createElement("button");
|
||||
this.delBtn.classList.add('delete');
|
||||
this.delBtn.classList.add('btn');
|
||||
this.delBtn.innerHTML = '<span class="fa fa-trash"></span>';
|
||||
this.third.append(this.delBtn);
|
||||
if (!this.model.has('upstream') ||
|
||||
this.upstreamCollection.where ({'uuid' : this.model.get('upstream')}).length === 0) {
|
||||
if (this.upstreamCollection.length > 0) {
|
||||
this.model.set('upstream', this.upstreamCollection.at(0).get('uuid'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.$el.append(this.first).append(this.second).append(this.third);
|
||||
},
|
||||
render: function() {
|
||||
$(this.key).val(this.model.get('hostname'));
|
||||
this.regenerate_list();
|
||||
$(this.value).val(this.model.get('upstream'));
|
||||
},
|
||||
deleteEntry: function (e) {
|
||||
e.preventDefault();
|
||||
this.collection.remove(this.model);
|
||||
},
|
||||
regenerate_list: function () {
|
||||
// backup value
|
||||
const v = $(this.value);
|
||||
// clear the dropdown
|
||||
v.html('');
|
||||
this.upstreamCollection.each(
|
||||
(mdl) => v.append(`<option value="${mdl.escape('uuid')}">${mdl.escape('description')}</option>`)
|
||||
);
|
||||
// restore
|
||||
v.val(this.model.get('upstream'));
|
||||
v.selectpicker('refresh');
|
||||
}
|
||||
});
|
||||
export const KeyValueMapFieldEntryACL = Backbone.View.extend({
|
||||
|
||||
tagName: 'div',
|
||||
attributes: {'class': 'row'},
|
||||
events: {
|
||||
'keyup .key': function () {
|
||||
this.model.set('network', this.key.value);
|
||||
},
|
||||
'change .value': function () {
|
||||
this.model.set('action', this.value.value);
|
||||
},
|
||||
"click .delete" : "deleteEntry"
|
||||
},
|
||||
key: null,
|
||||
value: null,
|
||||
delBtn: null,
|
||||
first: null,
|
||||
second: null,
|
||||
third: null,
|
||||
upstreamCollection: null,
|
||||
initialize: function (params) {
|
||||
this.upstreamCollection = params.upstreamCollection;
|
||||
this.listenTo(this.upstreamCollection, "update reset add remove", this.regenerate_list);
|
||||
this.first = document.createElement('div');
|
||||
this.first.classList.add('col-sm-5');
|
||||
this.key = document.createElement('input');
|
||||
this.first.append(this.key);
|
||||
this.key.type = 'text';
|
||||
this.key.classList.add('key');
|
||||
this.key.value = this.model.get('network');
|
||||
|
||||
this.second = document.createElement('div');
|
||||
this.second.classList.add('col-sm-5');
|
||||
this.value = document.createElement('select');
|
||||
this.second.append(this.value);
|
||||
this.value.classList.add('value');
|
||||
this.value.classList.add('form-control');
|
||||
this.value.value = this.model.get('action');
|
||||
|
||||
this.third = document.createElement('div');
|
||||
this.third.classList.add('col-sm-2');
|
||||
this.third.style.textAlign = 'right';
|
||||
this.delBtn = document.createElement("button");
|
||||
this.delBtn.classList.add('delete');
|
||||
this.delBtn.classList.add('btn');
|
||||
this.delBtn.innerHTML = '<span class="fa fa-trash"></span>';
|
||||
this.third.append(this.delBtn);
|
||||
|
||||
|
||||
this.$el.append(this.first).append(this.second).append(this.third);
|
||||
},
|
||||
render: function() {
|
||||
$(this.key).val(this.model.get('network'));
|
||||
this.regenerate_list();
|
||||
$(this.value).val(this.model.get('action'));
|
||||
},
|
||||
deleteEntry: function (e) {
|
||||
e.preventDefault();
|
||||
this.collection.remove(this.model);
|
||||
},
|
||||
regenerate_list: function () {
|
||||
// backup value
|
||||
const v = $(this.value);
|
||||
// clear the dropdown
|
||||
v.html('');
|
||||
this.upstreamCollection.each(
|
||||
(mdl) => v.append(`<option value="${mdl.escape('value')}">${mdl.escape('name')}</option>`)
|
||||
);
|
||||
// restore
|
||||
v.val(this.model.get('action'));
|
||||
v.selectpicker('refresh');
|
||||
}
|
||||
});
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
import accessLogLine from '../templates/AccessLogLine.html';
|
||||
import streamAccessLogLine from '../templates/StreamAccessLogLine.html';
|
||||
import errorLogLine from '../templates/ErrorLogLine.html';
|
||||
import logViewer from '../templates/logviewer.html';
|
||||
import LogLinesCollection from "../models/LogLinesCollection";
|
||||
import noDataAvailable from '../templates/noDataAvailable.html';
|
||||
|
||||
|
||||
const LogViewLine = Backbone.View.extend({
|
||||
|
|
@ -17,6 +19,8 @@ const LogViewLine = Backbone.View.extend({
|
|||
get_template: function() {
|
||||
if (this.type === 'accesses') {
|
||||
return accessLogLine;
|
||||
} else if (this.type === 'stream_accesses') {
|
||||
return streamAccessLogLine;
|
||||
} else {
|
||||
return errorLogLine;
|
||||
}
|
||||
|
|
@ -43,15 +47,21 @@ const LogView = Backbone.View.extend({
|
|||
render: function() {
|
||||
let tbody = this.$el.find('tbody');
|
||||
if (tbody.length < 1) {
|
||||
this.$el.html(logViewer({log_type: this.type, model: this.filter_model}));
|
||||
tbody = this.$el.find('tbody');
|
||||
if (this.collection.length !== 0) {
|
||||
this.$el.html(logViewer({log_type: this.type, model: this.filter_model}));
|
||||
tbody = this.$el.find('tbody');
|
||||
} else {
|
||||
this.$el.html(noDataAvailable);
|
||||
}
|
||||
}
|
||||
else {
|
||||
tbody.html('');
|
||||
}
|
||||
this.collection.filter_collection(this.filter_model).forEach(
|
||||
(model) => this.render_one(tbody, model)
|
||||
);
|
||||
if (this.collection.length !== 0) {
|
||||
this.collection.filter_collection(this.filter_model).forEach(
|
||||
(model) => this.render_one(tbody, model)
|
||||
);
|
||||
}
|
||||
},
|
||||
render_one: function(parent_element, model) {
|
||||
const logline = new LogViewLine({type: this.type, model: model});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
export default Backbone.Collection.extend({
|
||||
initialize: function() {
|
||||
let that = this;
|
||||
$('#ipacl\\.data').change(function () {
|
||||
that.regenerateFromView();
|
||||
});
|
||||
},
|
||||
regenerateFromView: function () {
|
||||
let data = $('#ipacl\\.data').data('data');
|
||||
if (!_.isArray(data)) {
|
||||
data = [];
|
||||
}
|
||||
this.reset(data);
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
export default Backbone.Model.extend({
|
||||
// standard model
|
||||
});
|
||||
|
|
@ -9,6 +9,12 @@ const LogLinesCollection = Backbone.Collection.extend({
|
|||
this.logType = 'none';
|
||||
this.uuid = 'none';
|
||||
},
|
||||
parse: function(response) {
|
||||
if ('error' in response) {
|
||||
return [];
|
||||
}
|
||||
return response;
|
||||
},
|
||||
filter_collection: function(filter_model) {
|
||||
const filter_model_keys = filter_model.keys();
|
||||
return this.filter(function (model) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
export default Backbone.Collection.extend({
|
||||
initialize: function() {
|
||||
let that = this;
|
||||
$('#snihostname\\.data').change(function () {
|
||||
that.regenerateFromView();
|
||||
});
|
||||
},
|
||||
regenerateFromView: function () {
|
||||
let data = $('#snihostname\\.data').data('data');
|
||||
if (!_.isArray(data)) {
|
||||
data = [];
|
||||
}
|
||||
this.reset(data);
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
export default Backbone.Model.extend({
|
||||
// standard model
|
||||
});
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
const UpstreamCollection = Backbone.Collection.extend({
|
||||
url: '/api/nginx/settings/searchupstream',
|
||||
parse: function(response) {
|
||||
return response.rows;
|
||||
}
|
||||
});
|
||||
export default UpstreamCollection;
|
||||
151
www/nginx/src/opnsense/www/js/nginx/src/nginx_config.js
Normal file
151
www/nginx/src/opnsense/www/js/nginx/src/nginx_config.js
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
import KeyValueMapField from './controller/KeyValueMapField';
|
||||
import UpstreamCollection from './models/UpstreamCollection';
|
||||
import {
|
||||
KeyValueMapFieldEntryACL,
|
||||
KeyValueMapFieldEntryUpstreamMap} from "./controller/KeyValueMapFieldEntry";
|
||||
import SNIHostnameUpstreamCollection from "./models/SNIHostnameUpstreamCollection";
|
||||
import SNIHostnameUpstreamModel from "./models/SNIHostnameUpstreamModel";
|
||||
import IPACLModel from "./models/IPACLModel";
|
||||
import IPACLCollection from "./models/IPACLCollection";
|
||||
|
||||
const uc = new UpstreamCollection();
|
||||
const actioncollection = new Backbone.Collection([
|
||||
{
|
||||
'name': 'Deny',
|
||||
'value': 'deny'
|
||||
},
|
||||
{
|
||||
'name': 'Allow',
|
||||
'value': 'allow'
|
||||
}
|
||||
]);
|
||||
|
||||
function bind_save_buttons() {
|
||||
// form save event handlers for all defined forms
|
||||
$('[id*="save_"]').each(function () {
|
||||
$(this).click(function () {
|
||||
let frm_id = $(this).closest("form").attr("id");
|
||||
let frm_title = $(this).closest("form").attr("data-title");
|
||||
// save data for General TAB
|
||||
saveFormToEndpoint("/api/nginx/settings/set", frm_id, function () {
|
||||
// on correct save, perform reconfigure. set progress animation when reloading
|
||||
$("#" + frm_id + "_progress").addClass("fa fa-spinner fa-pulse");
|
||||
|
||||
ajaxCall("/api/nginx/service/reconfigure", {}, function (data, status) {
|
||||
// when done, disable progress animation.
|
||||
$("#" + frm_id + "_progress").removeClass("fa fa-spinner fa-pulse");
|
||||
|
||||
if (data !== undefined && (status !== "success" || data['status'] !== 'ok')) {
|
||||
// fix error handling
|
||||
BootstrapDialog.show({
|
||||
type: BootstrapDialog.TYPE_WARNING,
|
||||
title: frm_title,
|
||||
message: JSON.stringify(data),
|
||||
draggable: true
|
||||
});
|
||||
} else {
|
||||
updateServiceControlUI('nginx');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function init_grids() {
|
||||
['upstream',
|
||||
'upstreamserver',
|
||||
'location',
|
||||
'credential',
|
||||
'userlist',
|
||||
'httpserver',
|
||||
'streamserver',
|
||||
'httprewrite',
|
||||
'custompolicy',
|
||||
'security_header',
|
||||
'ipacl',
|
||||
'limit_zone',
|
||||
'cache_path',
|
||||
'limit_request_connection',
|
||||
'snifwd',
|
||||
'naxsirule'].forEach(function (element) {
|
||||
$("#grid-" + element).UIBootgrid(
|
||||
{
|
||||
'search': '/api/nginx/settings/search' + element,
|
||||
'get': '/api/nginx/settings/get' + element + '/',
|
||||
'set': '/api/nginx/settings/set' + element + '/',
|
||||
'add': '/api/nginx/settings/add' + element + '/',
|
||||
'del': '/api/nginx/settings/del' + element + '/',
|
||||
'options': {selection: false, multiSelect: false}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function initSNIFieldComponent() {
|
||||
let snifield = new KeyValueMapField({
|
||||
dataField: document.getElementById('snihostname.data'),
|
||||
upstreamCollection: uc,
|
||||
entryclass: KeyValueMapFieldEntryUpstreamMap,
|
||||
collection: new SNIHostnameUpstreamCollection(),
|
||||
createModel: function () {
|
||||
return new SNIHostnameUpstreamModel({
|
||||
hostname: 'localhost',
|
||||
});
|
||||
}
|
||||
});
|
||||
window.snifield = snifield;
|
||||
snifield.render();
|
||||
$("#grid-upstream").on("loaded.rs.jquery.bootgrid", function () {
|
||||
/* we always have to reload too after bootgrid reloads */
|
||||
uc.fetch();
|
||||
});
|
||||
uc.fetch();
|
||||
}
|
||||
|
||||
$( document ).ready(function() {
|
||||
|
||||
let data_get_map = {'frm_nginx':'/api/nginx/settings/get'};
|
||||
|
||||
// load initial data
|
||||
mapDataToFormUI(data_get_map).done(function(){
|
||||
formatTokenizersUI();
|
||||
$('select[data-allownew="false"]').selectpicker('refresh');
|
||||
updateServiceControlUI('nginx');
|
||||
});
|
||||
|
||||
// update history on tab state and implement navigation
|
||||
if(window.location.hash !== "") {
|
||||
$('a[href="' + window.location.hash + '"]').click();
|
||||
}
|
||||
$('.nav-tabs a').on('shown.bs.tab', function (e) {
|
||||
history.pushState(null, null, e.target.hash);
|
||||
});
|
||||
|
||||
$('.reload_btn').click(function() {
|
||||
$(".reloadAct_progress").addClass("fa-spin");
|
||||
ajaxCall("/api/nginx/service/reconfigure", {}, function() {
|
||||
$(".reloadAct_progress").removeClass("fa-spin");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
bind_save_buttons();
|
||||
init_grids();
|
||||
bind_naxsi_rule_dl_button();
|
||||
initSNIFieldComponent();
|
||||
let ipaclfield = new KeyValueMapField({
|
||||
dataField: document.getElementById('ipacl.data'),
|
||||
upstreamCollection: actioncollection,
|
||||
entryclass: KeyValueMapFieldEntryACL,
|
||||
collection: new IPACLCollection(),
|
||||
createModel: function () {
|
||||
return new IPACLModel({
|
||||
network: '::',
|
||||
action: 'deny'
|
||||
});
|
||||
}
|
||||
});
|
||||
window.ipaclfield = ipaclfield;
|
||||
ipaclfield.render();
|
||||
});
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<td class="time"><%= model.escape('time') %></td>
|
||||
<td class="remote_ip"><%= model.escape('remote_ip') %></td>
|
||||
<td class="status"><%= model.escape('status') %></td>
|
||||
<td class="bytes_sent"><%= model.escape('bytes_sent') %></td>
|
||||
<td class="bytes_received"><%= model.escape('bytes_received') %></td>
|
||||
<td class="session_time"><%= model.escape('session_time') %></td>
|
||||
|
|
@ -45,7 +45,7 @@
|
|||
data-model-cid="<%= row.cid %>"
|
||||
data-model-uuid="<%= row.escape('id') %>"
|
||||
id="subtab_item_<%= row.escape('id') %>"
|
||||
href="#subtab_<%= row.escape('id') %>"><%= row.escape('server_name') %></a>
|
||||
href="#subtab_<%= row.escape('id') %>"><%= row.has("server_name") ? row.escape("server_name") : row.escape("port") %></a>
|
||||
</li>
|
||||
<% }) %>
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -29,5 +29,5 @@
|
|||
*/
|
||||
%>
|
||||
<a data-toggle="tab" href="#subtab_item_<%= model.escape('id') %>">
|
||||
<b><%= model.escape("server_name") %></b>
|
||||
<b><%= model.has("server_name") ? model.escape("server_name") : model.escape("port") %></b>
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<% if (log_type === 'errors') { %>
|
||||
<% if (log_type === 'errors' || log_type === 'stream_errors') { %>
|
||||
<th>Date</th>
|
||||
<th>Time</th>
|
||||
<th>Severity</th>
|
||||
<th>Number</th>
|
||||
<th>Message</th>
|
||||
<% } else { %>
|
||||
<% } else if (log_type === 'accesses') { %>
|
||||
<th>Time</th>
|
||||
<th>Remote IP</th>
|
||||
<th>Username</th>
|
||||
|
|
@ -17,16 +17,23 @@
|
|||
<th>User Agent</th>
|
||||
<th>Forwarded For</th>
|
||||
<th>Request Line</th>
|
||||
<% } else {%>
|
||||
<th>Time</th>
|
||||
<th>Remote IP</th>
|
||||
<th>Status</th>
|
||||
<th>Bytes Sent</th>
|
||||
<th>Bytes Received</th>
|
||||
<th>Session Time</th>
|
||||
<% } %>
|
||||
</tr>
|
||||
<tr class="filter">
|
||||
<% if (log_type === 'errors') { %>
|
||||
<% if (log_type === 'errors' || log_type === 'stream_errors') { %>
|
||||
<td><input type="text" value="<%= model.escape('date') %>" name="date" /></td>
|
||||
<td><input type="text" value="<%= model.escape('time') %>" name="time" /></td>
|
||||
<td><input type="text" value="<%= model.escape('severity') %>" name="severity" /></td>
|
||||
<td><input type="text" value="<%= model.escape('number') %>" name="number" /></td>
|
||||
<td><input type="text" value="<%= model.escape('message') %>" name="message" /></td>
|
||||
<% } else { %>
|
||||
<% } else if (log_type === 'accesses') { %>
|
||||
<td><input type="text" value="<%= model.escape('time') %>" name="time" /></td>
|
||||
<td><input type="text" value="<%= model.escape('remote_ip') %>" name="remote_ip" /></td>
|
||||
<td><input type="text" value="<%= model.escape('username') %>" name="username" /></td>
|
||||
|
|
@ -36,6 +43,13 @@
|
|||
<td><input type="text" value="<%= model.escape('user_agent') %>" name="user_agent" /></td>
|
||||
<td><input type="text" value="<%= model.escape('forwarded_for') %>" name="forwarded_for" /></td>
|
||||
<td><input type="text" value="<%= model.escape('request_line') %>" name="request_line" /></td>
|
||||
<% } else { %>
|
||||
<td><input type="text" value="<%= model.escape('time') %>" name="time" /></td>
|
||||
<td><input type="text" value="<%= model.escape('remote_ip') %>" name="remote_ip" /></td>
|
||||
<td><input type="text" value="<%= model.escape('status') %>" name="status" /></td>
|
||||
<td><input type="text" value="<%= model.escape('bytes_sent') %>" name="bytes_sent" /></td>
|
||||
<td><input type="text" value="<%= model.escape('bytes_received') %>" name="bytes_received" /></td>
|
||||
<td><input type="text" value="<%= model.escape('session_time') %>" name="session_time" /></td>
|
||||
<% } %>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
<div style="text-align: center;">
|
||||
No data avialable
|
||||
</div>
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
const path = require('path'), webpack = require('webpack');
|
||||
|
||||
module.exports = {
|
||||
entry: './src/logviewer.js',
|
||||
entry: {
|
||||
'logviewer': './src/logviewer.js',
|
||||
'configuration': './src/nginx_config.js'
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'bundle.js'
|
||||
filename: '[name].min.js'
|
||||
},
|
||||
mode: 'production',
|
||||
module: {
|
||||
|
|
|
|||
Loading…
Reference in a new issue