Add nginx listen addresses and default_server, fixes #973, #1218, #1675, #2574 (#2578)

- Changed listen_http_port, listen_https_port, listen_port to listen_http_address, listen_https_address, listen_address (issue #973, #2574)
- Migrated the old listen_X_port to the new listen_X_address
- Data Model ver 1.20.0 -> 1.21.0
- implemented default_server directive for non-tls listeners (issue #1218)
- amended Help Message
- added defaults for http server listen addresses
- added changelog
- fixed setup.php certificate setup
- fixed inexplicable missing Reconfigureaction in ServiceController
- fixed version numbers
- removed reconfigureaction in ServiceController again, because it was due to a code cleanup by fichtner and has to be fixed on a higher level
- added NgxUniqueDefaultServerConstraint which makes sure that default_servers do not conflict
- added i18n for the error message
- fixed some formatting issues
- added type hints
This commit is contained in:
Markus Peter
2021-11-22 08:52:34 +01:00
committed by GitHub
parent 0a06fa5a12
commit ee12197aca
13 changed files with 278 additions and 46 deletions
+1 -2
View File
@@ -1,6 +1,5 @@
PLUGIN_NAME= nginx
PLUGIN_VERSION= 1.23
PLUGIN_REVISION= 2
PLUGIN_VERSION= 1.24
PLUGIN_COMMENT= Nginx HTTP server and reverse proxy
PLUGIN_DEPENDS= nginx
PLUGIN_MAINTAINER= franz.fabian.94@gmail.com
+5
View File
@@ -10,6 +10,11 @@ WWW: https://nginx.org/
Plugin Changelog
================
1.24
* Change all Listen Port directives to Listen Address and migrate the Port data to Addresses
* Add default_server option to HTTP Server
1.23
* Add custom error pages on a per HTTP server basis (contributed by 8191)
@@ -170,7 +170,7 @@ class LogsController extends ApiControllerBase
{
$data = [];
foreach ($this->nginx->stream_server->iterateItems() as $item) {
$data[] = array('id' => $item->getAttributes()['uuid'], 'port' => (string)$item->listen_port);
$data[] = array('id' => $item->getAttributes()['uuid'], 'port' => (string)$item->listen_address);
}
return $data;
}
@@ -233,7 +233,7 @@ class SettingsController extends ApiMutableModelControllerBase
{
return $this->searchBase('http_server', array(
'servername', 'locations', 'root', 'https_only', 'certificate',
'listen_http_port', 'listen_https_port'
'listen_http_address', 'listen_https_address', 'default_server'
));
}
@@ -261,7 +261,7 @@ class SettingsController extends ApiMutableModelControllerBase
// stream server
public function searchstreamserverAction()
{
return $this->searchBase('stream_server', array('description', 'certificate', 'udp', 'listen_port'));
return $this->searchBase('stream_server', array('description', 'certificate', 'udp', 'listen_address'));
}
public function getstreamserverAction($uuid = null)
@@ -1,13 +1,24 @@
<form>
<field>
<id>httpserver.listen_http_port</id>
<label>HTTP Listen Port</label>
<type>text</type>
<id>httpserver.listen_http_address</id>
<label>HTTP Listen Address</label>
<allownew>true</allownew>
<style>tokenize</style>
<type>select_multiple</type>
<help>Enter a list of IP addresses and ports which can be used in nginx listen directives. To listen on a port on all IPs, use for example "80,[::]:80"</help>
</field>
<field>
<id>httpserver.listen_https_port</id>
<label>HTTPS Listen Port</label>
<type>text</type>
<id>httpserver.listen_https_address</id>
<label>HTTPS Listen Address</label>
<allownew>true</allownew>
<style>tokenize</style>
<type>select_multiple</type>
<help>Enter a list of IP addresses and ports which can be used in nginx listen directives. To listen on a port on all IPs, use for example "443,[::]:443"</help>
</field>
<field>
<id>httpserver.default_server</id>
<label>Default Server</label>
<type>checkbox</type>
</field>
<field>
<id>httpserver.syslog_targets</id>
@@ -1,8 +1,11 @@
<form>
<field>
<id>streamserver.listen_port</id>
<label>Listen Port</label>
<type>text</type>
<id>streamserver.listen_address</id>
<label>Listen Address</label>
<allownew>true</allownew>
<style>tokenize</style>
<type>select_multiple</type>
<help>Enter a list of IP addresses and ports which can be used in nginx listen directives. To listen on a port on all IPs, use for example "22,[::]:22"</help>
</field>
<field>
<id>streamserver.udp</id>
@@ -0,0 +1,134 @@
<?php
/*
* Copyright (C) 2021 Markus Peter mpeter at one-it.de
* 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\Base\Constraints;
use Phalcon\Messages\Message;
/**
* a very specific nginx check - not reusable
* it checks for the uniqueness of servers with the default_server directive
*
* Class NgxUniqueDefaultServerConstraint
* @package OPNsense\Nginx\Constraints
*/
class NgxUniqueDefaultServerConstraint extends BaseConstraint
{
public function validate(\Phalcon\Validation $validator, $attribute): bool
{
$node = $this->getOption('node');
if ($node) {
$httpServerNode = $node->getParentNode();
$defaultServerNode = $httpServerNode->getChild("default_server");
if (!$this->isEmpty($defaultServerNode))
{
$myUUID = $httpServerNode->getAttribute("uuid");
$myListenHTTPAddress = $httpServerNode->getChild("listen_http_address");
$httpServersNode = $httpServerNode->getParentNode();
$httpServers = $httpServersNode->getChildren();
$msg = "";
foreach ($httpServers as $httpServer)
{
$uuid = $httpServer->getAttribute("uuid");
if ($uuid != $myUUID)
{
$defaultServerNode = $httpServer->getChild("default_server");
if (!$this->isEmpty($defaultServerNode))
{
$listenHTTPAddressNode = $httpServer->getChild("listen_http_address");
if ($this->compareListenAddresses($myListenHTTPAddress, $listenHTTPAddressNode, $msg))
{
$validator->appendMessage(new Message(
sprintf(gettext("There can only be one Default Server on each listening address: %s conflict."), $msg),
$attribute
));
}
}
}
}
}
}
return true;
}
private function compareListenAddresses($as, $bs, &$msg): bool
{
foreach (explode(",", $as) as $a)
{
list($a_af, $a_ip, $a_port) = $this->extractAFIPPort($a);
foreach (explode(",", $bs) as $b)
{
list($b_af, $b_ip, $b_port) = $this->extractAFIPPort($b);
if ($a_af == $b_af && $a_port == $b_port)
{
if ($a_ip == null || $a_ip == "::" || $b_ip == null || $b_ip == "::" || $a_ip == $b_ip)
{
$msg = "IPv" . $a_af . ": [" . $a_ip . "]:" . $a_port . " and IPv" . $b_af . ": [" . $b_ip . "]:" . $b_port;
return true;
}
}
}
}
return false;
}
private function extractAFIPPort($in): array
{
$af = null;
$ip = null;
$port = null;
if (!strpos($in, ":"))
{
//if only number, then ipv4 port only
$af = 4;
$port = $in;
}
else
{
//extract ip and port
if (preg_match("/(?:([0-9.]+)|\[([0-9a-fA-F:]+)\]):(\d+)/", $in, $parts));
{
if (strpos($in, "[") === 0)
{
$af = 6;
$ip = inet_ntop(inet_pton($parts[2]));
$port = $parts[3];
}
else
{
$af = 4;
$ip = long2ip(ip2long($parts[1]));
$port = $parts[3];
}
}
}
return [$af, $ip, $port];
}
}
@@ -0,0 +1,59 @@
<?php
/**
* Copyright (C) 2021 Markus Peter mpeter at one-it.de
*
* 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\Migrations;
use OPNsense\Base\BaseModelMigration;
class M1_24_0 extends BaseModelMigration
{
public function run($model)
{
foreach ($model->getNodeByReference('http_server')->iterateItems() as $http_server) {
if ($http_server->listen_http_port != '')
{
$http_server->listen_http_address = $http_server->listen_http_port . ',[::]:' . $http_server->listen_http_port;
$http_server->listen_http_port = null;
}
if ($http_server->listen_https_port != '')
{
$http_server->listen_https_address = $http_server->listen_https_port . ',[::]:' . $http_server->listen_https_port;
$http_server->listen_https_port = null;
}
}
foreach ($model->getNodeByReference('stream_server')->iterateItems() as $server) {
if ($server->listen_port != '')
{
$server->listen_address = $server->listen_port . ',[::]:' . $server->listen_port;
$server->listen_port = null;
}
}
}
}
@@ -1,6 +1,6 @@
<model>
<mount>//OPNsense/Nginx</mount>
<version>1.20.1</version>
<version>1.24.0</version>
<description>nginx web server, reverse proxy and waf</description>
<items>
<general>
@@ -662,14 +662,34 @@
<Required>N</Required>
<multiple>Y</multiple>
</syslog_targets>
<listen_http_port type="PortField">
<listen_http_address type="CSVListField">
<Required>N</Required>
<default>80</default>
</listen_http_port>
<listen_https_port type="PortField">
<multiple>Y</multiple>
<default>80,[::]:80</default>
<mask>/^(?:(?:(?:\d{1,3}(?:\.\d{1,3}){3})|(?:\[[a-f0-9:]{1,4}(?::[a-f0-9:]{0,4}){1,7}\])):\d+|:?\d+)(?:\s*,\s*(?:(?:(?:\d{1,3}(?:\.\d{1,3}){3})|(?:\[[a-f0-9:]{1,4}(?::[a-f0-9:]{0,4}){1,7}\])):\d+|:?\d+))*$/i</mask>
<ValidationMessage>Please provide a valid listen address or port, i.e. 127.0.0.1:8080, [::1]:8080, 8080.</ValidationMessage>
<Constraints>
<check001>
<type>NgxUniqueDefaultServerConstraint</type>
</check001>
</Constraints>
</listen_http_address>
<listen_https_address type="CSVListField">
<Required>N</Required>
<default>443</default>
</listen_https_port>
<multiple>Y</multiple>
<default>443,[::]:443</default>
<mask>/^(?:(?:(?:\d{1,3}(?:\.\d{1,3}){3})|(?:\[[a-f0-9:]{1,4}(?::[a-f0-9:]{0,4}){1,7}\])):\d+|:?\d+)(?:\s*,\s*(?:(?:(?:\d{1,3}(?:\.\d{1,3}){3})|(?:\[[a-f0-9:]{1,4}(?::[a-f0-9:]{0,4}){1,7}\])):\d+|:?\d+))*$/i</mask>
<ValidationMessage>Please provide a valid listen address or port, i.e. 127.0.0.1:8080, [::1]:8080, 8080.</ValidationMessage>
</listen_https_address>
<default_server type="BooleanField">
<Required>Y</Required>
<default>0</default>
<Constraints>
<check001>
<type>NgxUniqueDefaultServerConstraint</type>
</check001>
</Constraints>
</default_server>
<proxy_protocol type="BooleanField">
<default>0</default>
<Required>Y</Required>
@@ -894,16 +914,12 @@
</http_server>
<stream_server type="ArrayField">
<listen_port type="PortField">
<listen_address type="CSVListField">
<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>
<multiple>Y</multiple>
<mask>/^(?:(?:(?:\d{1,3}(?:\.\d{1,3}){3})|(?:\[[a-f0-9:]{1,4}(?::[a-f0-9:]{0,4}){1,7}\])):\d+|:?\d+)(?:\s*,\s*(?:(?:(?:\d{1,3}(?:\.\d{1,3}){3})|(?:\[[a-f0-9:]{1,4}(?::[a-f0-9:]{0,4}){1,7}\])):\d+|:?\d+))*$/i</mask>
<ValidationMessage>Please provide a valid listen address or port, i.e. 127.0.0.1:8080, [::1]:8080, 8080.</ValidationMessage>
</listen_address>
<syslog_targets type="ModelRelationField">
<Model>
<template>
@@ -345,8 +345,9 @@
<th data-column-id="root" data-type="string" data-sortable="true" data-visible="false">{{ lang._('File System Root') }}</th>
<th data-column-id="certificate" data-type="string" data-sortable="true" data-visible="true">{{ lang._('Certificate') }}</th>
<th data-column-id="https_only" data-type="boolean" data-width="7em" data-sortable="true" data-visible="true">{{ lang._('HTTPS Only') }}</th>
<th data-column-id="listen_http_port" data-type="string" data-width="7em" data-sortable="true" data-visible="true">{{ lang._('HTTP Port') }}</th>
<th data-column-id="listen_https_port" data-type="string" data-width="7em" data-sortable="true" data-visible="true">{{ lang._('HTTPS Port') }}</th>
<th data-column-id="listen_http_address" data-type="string" data-width="7em" data-sortable="true" data-visible="true">{{ lang._('HTTP Address') }}</th>
<th data-column-id="listen_https_address" data-type="string" data-width="7em" data-sortable="true" data-visible="true">{{ lang._('HTTPS Address') }}</th>
<th data-column-id="default_server" data-type="string" data-width="7em" data-sortable="true" data-visible="true">{{ lang._('Default') }}</th>
<th data-column-id="commands" data-width="7em" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
</tr>
</thead>
@@ -369,7 +370,7 @@
<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="listen_address" data-type="string" data-sortable="true" data-visible="true">{{ lang._('Address') }}</th>
<th data-column-id="commands" data-width="7em" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
</tr>
</thead>
@@ -82,7 +82,7 @@ if (isset($nginx['http_server'])) {
$http_servers = array($nginx['http_server']);
}
foreach ($http_servers as $http_server) {
if (!empty($http_server['listen_https_port']) && !empty($http_server['certificate'])) {
if (!empty($http_server['listen_https_address']) && !empty($http_server['certificate'])) {
// try to find the reference
$cert = find_cert($http_server['certificate']);
if (!isset($cert)) {
@@ -127,7 +127,7 @@ if (isset($nginx['stream_server'])) {
$stream_servers = array($nginx['stream_server']);
}
foreach ($stream_servers as $stream_server) {
if (!empty($stream_server['listen_port']) && !empty($stream_server['certificate'])) {
if (!empty($stream_server['listen_address']) && !empty($stream_server['certificate'])) {
// try to find the reference
$cert = find_cert($stream_server['certificate']);
if (!isset($cert)) {
@@ -90,15 +90,17 @@ include opnsense_http_vhost_plugins/*.conf;
server {
{% set our_headers = [] %}
{% do our_headers.append('X-Powered-By') %}
{% if server.listen_http_port is defined %}
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) %}
{% if server.listen_http_address is defined and server.listen_http_address != '' %}
{% for listen_address in server.listen_http_address.split(',') %}
listen {{ listen_address }}{% if server.proxy_protocol is defined and server.proxy_protocol == '1' %} proxy_protocol{% endif %}{% if server.default_server is defined and server.default_server == '1' %} default_server{% endif %};
{% endfor %}
{% endif %}
{% if server.listen_https_port is defined and server.certificate is defined %}
listen {{ server.listen_https_port }} http2 ssl;
listen [::]:{{ server.listen_https_port }} http2 ssl;
{% do listen_list.append(server.listen_https_port) %}
{% if server.listen_https_address is defined and server.listen_https_address != '' and server.certificate is defined %}
{% for listen_address in server.listen_https_address.split(',') %}
listen {{ listen_address }} http2 ssl;
{% endfor %}
{% if server.ca is defined %}
ssl_client_certificate /usr/local/etc/nginx/key/{{ single_servername }}_ca.pem;
ssl_verify_client {{ server.verify_client }};
@@ -46,9 +46,11 @@
# 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 %};
{% if server.listen_address is defined and server.listen_address != '' %}
{% for listen_address in server.listen_address.split(',') %}
listen {{ listen_address }}{% 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 %};
{% endfor %}
{% endif %}
access_log /var/log/nginx/stream_{{ server['@uuid'] }}.access.log main;