net/haproxy: add HAProxy load balancer to plugins

This commit is contained in:
Frank Wall 2016-04-17 23:51:06 +02:00
parent a5fc77b2b0
commit e77e38bbcd
35 changed files with 5384 additions and 0 deletions

View file

View file

@ -0,0 +1,4 @@
echo "restarting configd..."
if /usr/local/etc/rc.d/configd status > /dev/null; then
/usr/local/etc/rc.d/configd restart
fi

View file

0
net/haproxy/+PRE_INSTALL Normal file
View file

7
net/haproxy/Makefile Normal file
View file

@ -0,0 +1,7 @@
PLUGIN_NAME= haproxy
PLUGIN_VERSION= 1.0
PLUGIN_COMMENT= Reliable, high performance TCP/HTTP load balancer
#PLUGIN_DEPENDS=
PLUGIN_MAINTAINER= opnsense@moov.de
.include "../../Mk/plugins.mk"

View file

@ -0,0 +1,168 @@
<?php
/**
* Copyright (C) 2016 Frank Wall
* Copyright (C) 2015 Deciso B.V.
*
* 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\HAProxy\Api;
use \OPNsense\Base\ApiControllerBase;
use \OPNsense\Core\Backend;
use \OPNsense\HAProxy\HAProxy;
/**
* Class ServiceController
* @package OPNsense\HAProxy
*/
class ServiceController extends ApiControllerBase
{
/**
* start haproxy service (in background)
* @return array
*/
public function startAction()
{
if ($this->request->isPost()) {
$backend = new Backend();
$response = $backend->configdRun("haproxy start", true);
return array("response" => $response);
} else {
return array("response" => array());
}
}
/**
* stop haproxy service
* @return array
*/
public function stopAction()
{
if ($this->request->isPost()) {
$backend = new Backend();
$response = $backend->configdRun("haproxy stop");
return array("response" => $response);
} else {
return array("response" => array());
}
}
/**
* restart haproxy service
* @return array
*/
public function restartAction()
{
if ($this->request->isPost()) {
$backend = new Backend();
$response = $backend->configdRun("haproxy restart");
return array("response" => $response);
} else {
return array("response" => array());
}
}
/**
* retrieve status of haproxy service
* @return array
* @throws \Exception
*/
public function statusAction()
{
$backend = new Backend();
$mdlProxy = new HAProxy();
$response = $backend->configdRun("haproxy status");
if (strpos($response, "not running") > 0) {
if ($mdlProxy->general->enabled->__toString() == 1) {
$status = "stopped";
} else {
$status = "disabled";
}
} elseif (strpos($response, "is running") > 0) {
$status = "running";
} elseif ($mdlProxy->general->enabled->__toString() == 0) {
$status = "disabled";
} else {
$status = "unkown";
}
return array("status" => $status);
}
/**
* reconfigure haproxy, generate config and reload
*/
public function reconfigureAction()
{
if ($this->request->isPost()) {
$force_restart = false;
// close session for long running action
$this->sessionClose();
$mdlProxy = new HAProxy();
$backend = new Backend();
$runStatus = $this->statusAction();
// stop haproxy when disabled
if ($runStatus['status'] == "running" &&
($mdlProxy->general->enabled->__toString() == 0 || $force_restart)) {
$this->stopAction();
}
// generate template
$backend->configdRun("template reload OPNsense.HAProxy");
// (res)start daemon
if ($mdlProxy->general->enabled->__toString() == 1) {
if ($runStatus['status'] == "running" && !$force_restart) {
$backend->configdRun("haproxy reconfigure");
} else {
$this->startAction();
}
}
return array("status" => "ok");
} else {
return array("status" => "failed");
}
}
/**
* run syntax check for haproxy configuration
* @return array
* @throws \Exception
*/
public function configtestAction()
{
$backend = new Backend();
// first generate template based on current configuration
$backend->configdRun("template reload OPNsense.HAProxy");
// now run the syntax check
$response = $backend->configdRun("haproxy configtest");
return array("result" => $response);
}
}

View file

@ -0,0 +1,77 @@
<?php
/**
* Copyright (C) 2016 Frank Wall
* Copyright (C) 2015 Deciso B.V.
*
* 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\HAProxy\Api;
use \OPNsense\Base\ApiControllerBase;
use \OPNsense\Core\Backend;
use \OPNsense\HAProxy\HAProxy;
/**
* Class StatisticsController
* @package OPNsense\HAProxy
*/
class StatisticsController extends ApiControllerBase
{
/**
* get info
* @return array|mixed
*/
public function infoAction($zoneid = 0)
{
$backend = new Backend();
$responseRaw = $backend->configdRun("haproxy statistics info");
$response = json_decode($responseRaw, true);
return $response;
}
/**
* get counters
* @return array|mixed
*/
public function countersAction($zoneid = 0)
{
$backend = new Backend();
$responseRaw = $backend->configdRun("haproxy statistics stat");
$response = json_decode($responseRaw, true);
return $response;
}
/**
* get tables
* @return array|mixed
*/
public function tablesAction($zoneid = 0)
{
$backend = new Backend();
$responseRaw = $backend->configdRun("haproxy statistics table");
$response = json_decode($responseRaw, true);
return $response;
}
}

View file

@ -0,0 +1,59 @@
<?php
/**
* Copyright (C) 2016 Frank Wall
* Copyright (C) 2015 Deciso B.V.
*
* 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\HAProxy;
/**
* Class IndexController
* @package OPNsense\HAProxy
*/
class IndexController extends \OPNsense\Base\IndexController
{
/**
* haproxy index page
* @throws \Exception
*/
public function indexAction()
{
// set page title
$this->view->title = "HAProxy Load Balancer";
// include form definitions
$this->view->mainForm = $this->getForm("main");
$this->view->formDialogFrontend = $this->getForm("dialogFrontend");
$this->view->formDialogBackend = $this->getForm("dialogBackend");
$this->view->formDialogServer = $this->getForm("dialogServer");
$this->view->formDialogHealthcheck = $this->getForm("dialogHealthcheck");
$this->view->formDialogAction = $this->getForm("dialogAction");
$this->view->formDialogAcl = $this->getForm("dialogAcl");
$this->view->formDialogLua = $this->getForm("dialogLua");
$this->view->formDialogErrorfile = $this->getForm("dialogErrorfile");
// pick the template to serve
$this->view->pick('OPNsense/HAProxy/index');
}
}

View file

@ -0,0 +1,44 @@
<?php
/**
* Copyright (C) 2016 Frank Wall
* Copyright (C) 2015 Deciso B.V.
*
* 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\HAProxy;
/**
* Class StatisticsController
* @package OPNsense\HAProxy
*/
class StatisticsController extends \OPNsense\Base\IndexController
{
public function indexAction()
{
$this->view->title = "HAProxy Load Balancer / Statistics";
// choose template
$this->view->pick('OPNsense/HAProxy/statistics');
}
}

View file

@ -0,0 +1,52 @@
<form>
<field>
<id>acl.name</id>
<label>Name</label>
<type>text</type>
<help>Name to identify this ACL.</help>
</field>
<field>
<id>acl.description</id>
<label>Description</label>
<type>text</type>
<help>Description for this ACL.</help>
</field>
<field>
<label>Compose expression</label>
<type>header</type>
</field>
<field>
<id>acl.expression</id>
<label>Expression</label>
<type>dropdown</type>
<hint>Select ACL expression.</hint>
</field>
<field>
<id>acl.negate</id>
<label>Negate condition</label>
<type>checkbox</type>
<help><![CDATA[Use this to invert the meaning of the expression.]]></help>
</field>
<field>
<id>acl.value</id>
<label>Value</label>
<type>text</type>
<help><![CDATA[Specify a value to match with the expression.]]></help>
</field>
<field>
<label>Optional parameters</label>
<type>header</type>
</field>
<field>
<id>acl.urlparam</id>
<label>URL parameter</label>
<type>text</type>
<help><![CDATA[Specify the URL parameter to be checked for the value specified below.<br/><b>Not used for any other expression.</b>]]></help>
</field>
<field>
<id>acl.queryBackend</id>
<label>Query Backend</label>
<type>dropdown</type>
<help><![CDATA[Use this backend to count usable servers.<br/><b>Not used for any other expression.</b>]]></help>
</field>
</form>

View file

@ -0,0 +1,81 @@
<form>
<field>
<id>action.name</id>
<label>Name</label>
<type>text</type>
<help>Name to identify this action.</help>
</field>
<field>
<id>action.description</id>
<label>Description</label>
<type>text</type>
<help>Description for this action.</help>
</field>
<field>
<label>Form condition</label>
<type>header</type>
</field>
<field>
<id>action.testType</id>
<label>Test type</label>
<type>dropdown</type>
<help><![CDATA[Choose how to test. By using IF it tests if the condition evaluates to true. If you use UNLESS, the sense of the test is reversed.]]></help>
</field>
<field>
<id>action.linkedAcls</id>
<label>Select ACLs</label>
<type>select_multiple</type>
<style>tokenize</style>
<help><![CDATA[Select one ore more ACLs to be used as condition for this action.]]></help>
</field>
<field>
<id>action.operator</id>
<label>Logical operator (for ACLs)</label>
<type>dropdown</type>
<help><![CDATA[Choose an logical operator to be used to form a condition.]]></help>
</field>
<field>
<id>action.type</id>
<label>Choose action</label>
<type>dropdown</type>
<help><![CDATA[Choose an action that should be executed if the condition is true.]]></help>
</field>
<field>
<label>Optional parameters</label>
<type>header</type>
</field>
<field>
<id>action.useBackend</id>
<label>Use backend</label>
<type>dropdown</type>
<help><![CDATA[Use this backend if the condition is true.<br/><b>Not used for any other action.</b>]]></help>
</field>
<field>
<id>action.useServer</id>
<label>Use server</label>
<type>dropdown</type>
<help><![CDATA[Use this server if the condition is true.<br/><b>Not used for any other action.</b>]]></help>
</field>
<field>
<label>Conditional parameters</label>
<type>header</type>
</field>
<field>
<id>action.actionName</id>
<label>Name/Identifier</label>
<type>text</type>
<help><![CDATA[Specify a value to match with the action.]]></help>
</field>
<field>
<id>action.actionFind</id>
<label>Find value</label>
<type>text</type>
<help><![CDATA[Specify a value to match with the action.]]></help>
</field>
<field>
<id>action.actionValue</id>
<label>Set value</label>
<type>text</type>
<help><![CDATA[Specify a value to match with the action.]]></help>
</field>
</form>

View file

@ -0,0 +1,157 @@
<form>
<field>
<id>backend.enabled</id>
<label>Enabled</label>
<type>checkbox</type>
<help>Enable this backend</help>
</field>
<field>
<id>backend.name</id>
<label>Name</label>
<type>text</type>
<help>Name to identify this backend.</help>
</field>
<field>
<id>backend.description</id>
<label>Description</label>
<type>text</type>
<help>Description for this backend.</help>
</field>
<field>
<id>backend.mode</id>
<label>Mode</label>
<type>dropdown</type>
<help><![CDATA[Set the running mode or protocol of the backend. Usually the frontend and the backend are in the same mode.]]></help>
<hint>Set the same mode for backend and frontend.</hint>
</field>
<field>
<id>backend.algorithm</id>
<label>Balancing Algorithm</label>
<type>dropdown</type>
<help><![CDATA[Define the load balancing algorithm to be used in a backend. See the <a target="_blank" href="http://cbonte.github.io/haproxy-dconv/configuration-1.6.html#balance">HAProxy documentation</a> for a full description.]]></help>
<hint>Choose a load balancing algorithm.</hint>
</field>
<field>
<id>backend.linkedServers</id>
<label>Servers</label>
<type>select_multiple</type>
<style>tokenize</style>
<allownew>true</allownew>
<help><![CDATA[Add servers to this backend. Use TAB key to complete typing.]]></help>
<hint>Type server name or choose from list.</hint>
</field>
<field>
<label>Health Checking</label>
<type>header</type>
</field>
<field>
<id>backend.healthCheckEnabled</id>
<label>Enabled</label>
<type>checkbox</type>
<help><![CDATA[Enable or disable health checking.]]></help>
</field>
<field>
<id>backend.healthCheck</id>
<label>Health check</label>
<type>dropdown</type>
<help><![CDATA[Select health check for servers in this backend.]]></help>
</field>
<field>
<id>backend.healthCheckLogStatus</id>
<label>Log Status Changes</label>
<type>checkbox</type>
<help><![CDATA[Enable to log health check status updates.]]></help>
</field>
<field>
<label>Stick-table persistence</label>
<type>header</type>
</field>
<field>
<id>backend.stickiness_pattern</id>
<label>Table type</label>
<type>dropdown</type>
<help><![CDATA[Choose a request pattern to associate a user to a server. See the <a target="_blank" href="http://cbonte.github.io/haproxy-dconv/configuration-1.6.html#stick on">HAProxy documentation</a> for a full description.<br/><div class="text-info"><b>NOTE:</b> Consider not using this feature in multi-process mode, it can result in random behaviours.</div>]]></help>
<hint>Choose a persistence type.</hint>
</field>
<field>
<id>backend.stickiness_expire</id>
<label>Expiration time</label>
<type>text</type>
<help><![CDATA[Enter a number followed by one of the supported suffixes "d" (days), "h" (hour), "m" (minute), "s" (seconds), "ms" (miliseconds). This configures the maximum duration of an entry in the stick-table since it was last created, refreshed or matched. The maximum duration is slightly above 24 days.]]></help>
<advanced>true</advanced>
</field>
<field>
<id>backend.stickiness_size</id>
<label>Size</label>
<type>text</type>
<help><![CDATA[Enter a number followed by one of the supported suffixes "k", "m", "g". This configures the maximum number of entries that can fit in the table. This value directly impacts memory usage. Count approximately 50 bytes per entry, plus the size of a string if any.]]></help>
<advanced>true</advanced>
</field>
<field>
<id>backend.stickiness_cookiename</id>
<label>Cookie name</label>
<type>text</type>
<help><![CDATA[Cookie name to use for stick table (if appropiate table type is selected).]]></help>
</field>
<field>
<id>backend.stickiness_cookielength</id>
<label>Cookie length</label>
<type>text</type>
<help><![CDATA[The maximum number of characters that will be stored in the stick table (if appropiate table type is selected).]]></help>
</field>
<field>
<label>Tuning Options</label>
<type>header</type>
</field>
<field>
<id>backend.tuning_timeoutConnect</id>
<label>Connection Timeout</label>
<type>text</type>
<help><![CDATA[Set the maximum time (in milliseconds) to wait for a connection attempt to a server to succeed.]]></help>
<advanced>true</advanced>
</field>
<field>
<id>backend.tuning_timeoutServer</id>
<label>Server Timeout</label>
<type>text</type>
<help><![CDATA[Set the maximum inactivity time (in milliseconds) on the server side.]]></help>
<advanced>true</advanced>
</field>
<field>
<id>backend.tuning_retries</id>
<label>Retries</label>
<type>text</type>
<help><![CDATA[Set the number of retries to perform on a server after a connection failure.]]></help>
</field>
<field>
<id>backend.customOptions</id>
<label>Option pass-through</label>
<type>textbox</type>
<help><![CDATA[These lines will be added to the HAProxy backend configuration.<br/><div class="text-info"><b>NOTE:</b> The syntax will not be checked, use at your own risk!</div>]]></help>
<advanced>true</advanced>
</field>
<field>
<label>Actions (ACLs)</label>
<type>header</type>
</field>
<field>
<id>backend.linkedActions</id>
<label>Actions</label>
<type>select_multiple</type>
<style>tokenize</style>
<help><![CDATA[Choose actions to be included in this backend.]]></help>
<hint>Choose actions.</hint>
</field>
<field>
<label>Error Files</label>
<type>header</type>
</field>
<field>
<id>backend.linkedErrorfiles</id>
<label>Error files</label>
<type>select_multiple</type>
<style>tokenize</style>
<help><![CDATA[Choose error files to be included in this backend.]]></help>
<hint>Choose error files.</hint>
</field>
</form>

View file

@ -0,0 +1,26 @@
<form>
<field>
<id>errorfile.name</id>
<label>Name</label>
<type>text</type>
<help>Name to identify this error file.</help>
</field>
<field>
<id>errorfile.description</id>
<label>Description</label>
<type>text</type>
<help>Description for this error file.</help>
</field>
<field>
<id>errorfile.code</id>
<label>Error code</label>
<type>dropdown</type>
<help>The HTTP status code.</help>
</field>
<field>
<id>errorfile.content</id>
<label>Content</label>
<type>textbox</type>
<help>Paste the content of your errorfile here. The files should not exceed the configured buffer size, which generally is 8 or 16 kB.</help>
</field>
</form>

View file

@ -0,0 +1,170 @@
<form>
<field>
<id>frontend.enabled</id>
<label>Enabled</label>
<type>checkbox</type>
<help>Enable this frontend</help>
</field>
<field>
<id>frontend.name</id>
<label>Name</label>
<type>text</type>
<help>Name to identify this frontend.</help>
</field>
<field>
<id>frontend.description</id>
<label>Description</label>
<type>text</type>
<help>Description for this frontend.</help>
</field>
<field>
<id>frontend.bind</id>
<label>Listen Addresses</label>
<type>select_multiple</type>
<style>tokenize</style>
<allownew>true</allownew>
<help><![CDATA[Configure listen addresses for this frontend, i.e. 127.0.0.1:8080 or www.example.com:443. Use TAB key to complete typing a listen address.]]></help>
<hint>Enter address:port here. Finish with TAB.</hint>
</field>
<field>
<id>frontend.mode</id>
<label>Type</label>
<type>dropdown</type>
<help><![CDATA[Set the running mode or protocol for this frontend.]]></help>
</field>
<field>
<id>frontend.defaultBackend</id>
<label>Default Backend</label>
<type>dropdown</type>
<help><![CDATA[Set the default backend to use for this frontend.]]></help>
</field>
<field>
<label>SSL Offloading</label>
<type>header</type>
</field>
<field>
<id>frontend.ssl_enabled</id>
<label>Enabled</label>
<type>checkbox</type>
<help>Enable SSL offloading</help>
</field>
<field>
<id>frontend.ssl_certificates</id>
<label>Certificates</label>
<type>select_multiple</type>
<style>tokenize</style>
<allownew>true</allownew>
<help><![CDATA[Select certificates to use for SSL offloading. HAProxy's SNI recognition will determine the correct certificate automatically. If no SNI is provided by the client then the first certificate will be presented.<br/>To import additional certificates, go to <a href="/system_certmanager.php">Certificate Manager</a>.]]></help>
<hint>Type certificate name or choose from list.</hint>
</field>
<field>
<id>frontend.ssl_customOptions</id>
<label>Advanced SSL options</label>
<type>text</type>
<help><![CDATA[Specify additional SSL parameters such as force-sslv3, force-tlsv10, force-tlsv11, force-tlsv12, no-sslv3, no-tlsv10, no-tlsv11, no-tlsv12, no-tls-tickets or customize the list of SSL ciphers.<br/>Example: no-sslv3 ciphers HIGH:!DSS:!aNULL@STRENGTH<br/><div class="text-info"><b>NOTE:</b> The syntax will not be checked, use at your own risk!</div>]]></help>
<advanced>true</advanced>
</field>
<field>
<label>Tuning Options</label>
<type>header</type>
</field>
<field>
<id>frontend.tuning_maxConnections</id>
<label>Max. Connections</label>
<type>text</type>
<help><![CDATA[Set the maximum number of concurrent connections for this frontend.]]></help>
</field>
<field>
<id>frontend.tuning_timeoutClient</id>
<label>Client Timeout</label>
<type>text</type>
<help><![CDATA[Set the maximum inactivity time (in milliseconds) on the client side.]]></help>
<advanced>true</advanced>
</field>
<field>
<label>Logging Options</label>
<type>header</type>
</field>
<field>
<id>frontend.logging_dontLogNull</id>
<label>Don't log null</label>
<type>checkbox</type>
<help><![CDATA[Enable or disable logging of connections with no data.]]></help>
<advanced>true</advanced>
</field>
<field>
<id>frontend.logging_dontLogNormal</id>
<label>Don't log normal</label>
<type>checkbox</type>
<help><![CDATA[Enable or disable logging of normal, successful connections.]]></help>
<advanced>true</advanced>
</field>
<field>
<id>frontend.logging_logSeparateErrors</id>
<label>Raise Log Level</label>
<type>checkbox</type>
<help><![CDATA[Allow HAProxy to automatically raise log level for non-completely successful connections to aid debugging.]]></help>
<advanced>true</advanced>
</field>
<field>
<id>frontend.logging_detailedLog</id>
<label>Detailed Logging</label>
<type>checkbox</type>
<help><![CDATA[Enable or disable verbose logging. Each log line turns into a much richer format.]]></help>
</field>
<field>
<id>frontend.logging_socketStats</id>
<label>Separate Statistics</label>
<type>checkbox</type>
<help><![CDATA[Enable or disable collecting & providing separate statistics for each socket.]]></help>
<advanced>true</advanced>
</field>
<field>
<label>Advanced settings</label>
<type>header</type>
</field>
<field>
<id>frontend.forwardFor</id>
<label>X-Forwarded-For header</label>
<type>checkbox</type>
<help><![CDATA[Enable insertion of the X-Forwarded-For header to requests sent to servers.]]></help>
</field>
<field>
<id>frontend.connectionBehaviour</id>
<label>Type</label>
<type>dropdown</type>
<help><![CDATA[By default HAProxy operates in <b>keep-alive</b> mode with regards to persistent connections. Option <b>"http-tunnel"</b> disables any HTTP processing past the first request and the first response. Option <b>"httpclose"</b> configures HAProxy to work in HTTP tunnel mode and check if a "Connection: close" header is already set in each direction, and will add one if missing. Option <b>"http-server-close"</b> enables HTTP connection-close mode on the server side while keeping the ability to support HTTP keep-alive and pipelining on the client side. With Option <b>"forceclose"</b> HAProxy will actively close the outgoing server channel as soon as the server has finished to respond and release some resources earlier.]]></help>
<advanced>true</advanced>
</field>
<field>
<id>frontend.customOptions</id>
<label>Option pass-through</label>
<type>textbox</type>
<help><![CDATA[These lines will be added to the HAProxy frontend configuration.<br/><div class="text-info"><b>NOTE:</b> The syntax will not be checked, use at your own risk!</div>]]></help>
<advanced>true</advanced>
</field>
<field>
<label>Actions (ACLs)</label>
<type>header</type>
</field>
<field>
<id>frontend.linkedActions</id>
<label>Actions</label>
<type>select_multiple</type>
<style>tokenize</style>
<help><![CDATA[Choose actions to be included in this frontend.]]></help>
<hint>Choose actions.</hint>
</field>
<field>
<label>Error Files</label>
<type>header</type>
</field>
<field>
<id>frontend.linkedErrorfiles</id>
<label>Error files</label>
<type>select_multiple</type>
<style>tokenize</style>
<help><![CDATA[Choose error files to be included in this backend.]]></help>
<hint>Choose error files.</hint>
</field>
</form>

View file

@ -0,0 +1,106 @@
<form>
<field>
<id>healthcheck.name</id>
<label>Name</label>
<type>text</type>
<help>Name to identify this ACL.</help>
</field>
<field>
<id>healthcheck.description</id>
<label>Description</label>
<type>text</type>
<help>Description for this ACL.</help>
</field>
<field>
<id>healthcheck.type</id>
<label>Check type</label>
<type>dropdown</type>
<help><![CDATA[Select type of health check.]]></help>
</field>
<field>
<id>healthcheck.interval</id>
<label>Check interval</label>
<type>text</type>
<help><![CDATA[Select interval (in milliseconds) between two consecutive health checks.]]></help>
</field>
<field>
<label>HTTP check options</label>
<type>header</type>
</field>
<field>
<id>healthcheck.http_method</id>
<label>HTTP method</label>
<type>dropdown</type>
<help><![CDATA[Select HTTP method for health check.]]></help>
</field>
<field>
<id>healthcheck.http_uri</id>
<label>Request URI</label>
<type>text</type>
<help><![CDATA[Specify HTTP request URI for health check.]]></help>
</field>
<field>
<id>healthcheck.http_version</id>
<label>HTTP version</label>
<type>dropdown</type>
<help><![CDATA[Select HTTP version for a HTTP health check.]]></help>
</field>
<field>
<id>healthcheck.http_host</id>
<label>HTTP host</label>
<type>text</type>
<help><![CDATA[Specify HTTP host to use for health check. Requires HTTP/1.1.]]></help>
</field>
<field>
<label>Custom HTTP check</label>
<type>header</type>
</field>
<field>
<id>healthcheck.http_expressionEnabled</id>
<label>Enabled</label>
<type>checkbox</type>
</field>
<field>
<id>healthcheck.http_expression</id>
<label>Expression</label>
<type>dropdown</type>
<help><![CDATA[Select health check expression.]]></help>
</field>
<field>
<id>healthcheck.http_negate</id>
<label>Negate condition</label>
<type>checkbox</type>
<help><![CDATA[Use this to invert the meaning of the expression.]]></help>
</field>
<field>
<id>healthcheck.http_value</id>
<label>Value</label>
<type>text</type>
<help><![CDATA[Specify a value to match with the expression. <br/><div class="text-info"><b>NOTE:</b> It is important to note that the responses will be limited to a certain size defined by the global "tune.chksize" option, which defaults to 16384 bytes.</div>]]></help>
<help><![CDATA[Specify additional SSL parameters such as force-sslv3, force-tlsv10, force-tlsv11, force-tlsv12, no-sslv3, no-tlsv10, no-tlsv11, no-tlsv12, no-tls-tickets or customize the list of SSL ciphers.<br/>Example: no-sslv3 ciphers HIGH:!DSS:!aNULL@STRENGTH<br/><div class="text-info"><b>NOTE:</b> The syntax will not be checked, use at your own risk!</div>]]></help>
</field>
<field>
<label>Non-HTTP check options</label>
<type>header</type>
</field>
<field>
<id>healthcheck.agentPort</id>
<label>Agent port</label>
<type>text</type>
<help><![CDATA[Specify the TCP port used for agent checks.]]></help>
</field>
<field>
<id>healthcheck.dbUser</id>
<label>DB user</label>
<type>text</type>
<help><![CDATA[Specify the username to be used for database health checks.]]></help>
</field>
<field>
<id>healthcheck.smtpDomain</id>
<label>SMTP domain</label>
<type>text</type>
<help><![CDATA[Specify the domain name to present to the server for SMTP/ESMTP health checks.]]></help>
</field>
</form>

View file

@ -0,0 +1,26 @@
<form>
<field>
<id>lua.enabled</id>
<label>Enabled</label>
<type>checkbox</type>
<help>Enable this Lua script.</help>
</field>
<field>
<id>lua.name</id>
<label>Name</label>
<type>text</type>
<help>Name to identify this Lua script.</help>
</field>
<field>
<id>lua.description</id>
<label>Description</label>
<type>text</type>
<help>Description for this Lua script.</help>
</field>
<field>
<id>lua.content</id>
<label>Content</label>
<type>textbox</type>
<help>Paste the content of your Lua script here.</help>
</field>
</form>

View file

@ -0,0 +1,54 @@
<form>
<field>
<id>server.name</id>
<label>Name</label>
<type>text</type>
<help>Name to identify this server.</help>
</field>
<field>
<id>server.description</id>
<label>Description</label>
<type>text</type>
<help>Description for this server.</help>
</field>
<field>
<id>server.address</id>
<label>FQDN or IP</label>
<type>text</type>
<help><![CDATA[Provide either the FQDN or the IP address of this server.]]></help>
<hint>Enter server address.</hint>
</field>
<field>
<id>server.port</id>
<label>Port</label>
<type>text</type>
<help><![CDATA[Provide the TCP communication port for this server, i.e. 80 or 443.]]></help>
</field>
<field>
<id>server.mode</id>
<label>Mode</label>
<type>dropdown</type>
<help><![CDATA[Sets the operation mode to use for this server.]]></help>
<advanced>true</advanced>
</field>
<field>
<id>server.ssl</id>
<label>SSL</label>
<type>checkbox</type>
<help><![CDATA[Enable or disable SSL communication with this server.]]></help>
</field>
<field>
<id>server.weight</id>
<label>Weight</label>
<type>text</type>
<help><![CDATA[Adjust the server's weight relative to other servers.]]></help>
<advanced>true</advanced>
</field>
<field>
<id>server.checkInterval</id>
<label>Check Interval</label>
<type>text</type>
<help><![CDATA[Sets the interval (in milliseconds) for running health checks on the server.]]></help>
<advanced>true</advanced>
</field>
</form>

View file

@ -0,0 +1,190 @@
<form>
<tab id="haproxy-general" description="General Settings">
<subtab id="haproxy-general-settings" description="Service Settings">
<field>
<label>NOTE: You need to configure frontends, backends and servers before enabling HAProxy.</label>
<type>info</type>
</field>
<field>
<id>haproxy.general.enabled</id>
<label>Enable HAProxy</label>
<type>checkbox</type>
<help>Enable or disable the HAProxy service.</help>
</field>
</subtab>
<subtab id="haproxy-general-global" description="Global Parameters">
<field>
<label>NOTE: Define global parameters for the HAProxy service. They cannot be overriden.</label>
<type>info</type>
</field>
<field>
<id>haproxy.general.tuning.nbproc</id>
<label>HAProxy processes</label>
<type>text</type>
<help><![CDATA[Number of HAProxy processes to start.<br/><div class="text-info"><b>NOTE:</b> You may experience random issues in multi-process mode. For more information about the "nbproc" option please see the HAProxy Documentation.</div>]]></help>
<advanced>true</advanced>
</field>
<field>
<id>haproxy.general.tuning.maxConnections</id>
<label>Maximum connections</label>
<type>text</type>
<help><![CDATA[Sets the maximum number of concurrent connections per HAProxy process.<br/><div class="text-info"><b>NOTE:</b> HAProxy will not be able to allocate enough memory if you set this value too high. Consider raising the settings for kern.maxfiles and kern.maxfilesperproc if you need to specify a non-default value.</div>]]></help>
</field>
<field>
<id>haproxy.general.tuning.maxDHSize</id>
<label>Maximum SSL DH Size</label>
<type>text</type>
<help><![CDATA[Sets the maximum size of the Diffie-Hellman parameters used for generating the ephemeral/temporary Diffie-Hellman key in case of DHE key exchange (default is 1024).<br/><div class="text-info"><b>NOTE:</b> Higher values will increase the CPU load. For more information about the "tune.ssl.default-dh-param" option please see the HAProxy Documentation.</div>]]></help>
</field>
<field>
<id>haproxy.general.tuning.bufferSize</id>
<label>Buffer size</label>
<type>text</type>
<help><![CDATA[Change the buffer size (in bytes). Lower values allow more sessions to coexist in the same amount of RAM, and higher values allow some applications with very large cookies to work. The default value is 16384. <br/><div class="text-info"><b>NOTE:</b> It is strongly recommended not to change this from the default value, as very low values will break some services such as statistics, and values larger than default size will increase memory usage, possibly causing the system to run out of memory.</div>]]></help>
<advanced>true</advanced>
</field>
<field>
<id>haproxy.general.tuning.checkBufferSize</id>
<label>Health check buffer size</label>
<type>text</type>
<help><![CDATA[Change the check buffer size (in bytes). Higher values may help find string or regex patterns in very large pages, though doing so may imply more memory and CPU usage. The default value is 16384.]]></help>
<advanced>true</advanced>
</field>
<field>
<id>haproxy.general.tuning.luaMaxMem</id>
<label>Maximum RAM per LUA process</label>
<type>text</type>
<help><![CDATA[Sets the maximum amount of RAM in megabytes per process usable by Lua. By default it is zero which means unlimited. It is important to set a limit to ensure that a bug in a script will not result in the system running out of memory.]]></help>
<advanced>true</advanced>
</field>
<field>
<id>haproxy.general.tuning.spreadChecks</id>
<label>Spread checks</label>
<type>text</type>
<help><![CDATA[Add some randomness in the check interval between 0 and +/- 50%. A value between 2 and 5 seems to show good results. The default value is 0 (disabled).]]></help>
</field>
<field>
<id>haproxy.general.tuning.customOptions</id>
<label>Custom options</label>
<type>textbox</type>
<help><![CDATA[These lines will be added to the global settings of to the HAProxy configuration file.<br/><div class="text-info"><b>NOTE:</b> The syntax will not be checked, use at your own risk!</div>]]></help>
<advanced>true</advanced>
</field>
</subtab>
<subtab id="haproxy-general-defaults" description="Default Parameters">
<field>
<label>NOTE: Define default parameters for ALL Frontends, Backends and Servers here. They may still be overriden elsewhere.</label>
<type>info</type>
</field>
<field>
<id>haproxy.general.defaults.maxConnections</id>
<label>Max. Connections</label>
<type>text</type>
<help><![CDATA[Set the maximum number of concurrent connections for this frontend.]]></help>
</field>
<field>
<id>haproxy.general.defaults.timeoutClient</id>
<label>Client Timeout</label>
<type>text</type>
<help><![CDATA[Set the maximum inactivity time (in milliseconds) on the client side.]]></help>
</field>
<field>
<id>haproxy.general.defaults.timeoutConnect</id>
<label>Connection Timeout</label>
<type>text</type>
<help><![CDATA[Set the maximum time (in milliseconds) to wait for a connection attempt to a server to succeed.]]></help>
</field>
<field>
<id>haproxy.general.defaults.timeoutServer</id>
<label>Server Timeout</label>
<type>text</type>
<help><![CDATA[Set the maximum inactivity time (in milliseconds) on the server side.]]></help>
</field>
<field>
<id>haproxy.general.defaults.retries</id>
<label>Retries</label>
<type>text</type>
<help><![CDATA[Set the number of retries to perform on a server after a connection failure (default is 3).]]></help>
</field>
<field>
<id>haproxy.general.defaults.redispatch</id>
<label>Session redistribution</label>
<type>dropdown</type>
<help><![CDATA[Enable or disable session redistribution in case of connection failure.]]></help>
</field>
</subtab>
<subtab id="haproxy-general-logging" description="Logging Configuration">
<field>
<id>haproxy.general.logging.host</id>
<label>Log Host</label>
<type>text</type>
<help><![CDATA[Indicates where to send the logs. Takes an IPv4 or IPv6 address optionally followed by a colon (':') and a UDP port, i.e. 127.0.0.1 or 10.0.0.1:514]]></help>
</field>
<field>
<id>haproxy.general.logging.facility</id>
<label>Syslog facility</label>
<type>dropdown</type>
<help><![CDATA[Choose one of the 24 standard syslog facilities. The default value is local0.]]></help>
</field>
<field>
<id>haproxy.general.logging.level</id>
<label>Filter syslog level</label>
<type>dropdown</type>
<help><![CDATA[Can be specified to filter outgoing messages. By default, all messages are sent. If a level is specified, only messages with a severity at least as important as this level will be sent.]]></help>
</field>
<field>
<id>haproxy.general.logging.length</id>
<label>Max. line length</label>
<type>text</type>
<help><![CDATA[Specify an optional maximum line length. Log lines larger than this value will be truncated before being sent. The reason is that syslog servers act differently on log line length. All servers support the default value of 1024, but some servers simply drop larger lines while others do log them.]]></help>
<advanced>true</advanced>
</field>
</subtab>
<subtab id="haproxy-general-statistics" description="Statistics Configuration">
<field>
<id>haproxy.general.stats.enabled</id>
<label>Stats enabled</label>
<type>checkbox</type>
<help><![CDATA[Enable HAProxy's statistics page.]]></help>
</field>
<field>
<id>haproxy.general.stats.port</id>
<label>Local stats TCP port</label>
<type>text</type>
<help><![CDATA[Choose a TCP port to be used for the local statistics page. The default value is 8822.]]></help>
<advanced>true</advanced>
</field>
<field>
<id>haproxy.general.stats.remoteEnabled</id>
<label>Enable remote access</label>
<type>checkbox</type>
<help><![CDATA[Enable remote access to HAProxy's statistics page. <b>This may be a security risk if you do not enable authentication!</b> Note that you need to add appropiate firewall rules for this to work.]]></help>
</field>
<field>
<id>haproxy.general.stats.remoteBind</id>
<label>Remote listen addresses</label>
<type>select_multiple</type>
<style>tokenize</style>
<allownew>true</allownew>
<help><![CDATA[Configure listen addresses for the statistics page to enable remote access, i.e. 10.0.0.1:8080 or haproxy.example.com:8999. Use TAB key to complete typing a listen address.]]></help>
<hint>Enter address:port here. Finish with TAB.</hint>
</field>
<field>
<id>haproxy.general.stats.authEnabled</id>
<label>Enable authentication</label>
<type>checkbox</type>
</field>
<field>
<id>haproxy.general.stats.users</id>
<label>Stats users</label>
<type>select_multiple</type>
<style>tokenize</style>
<allownew>true</allownew>
<help><![CDATA[Grant access to HAProxy statistics page. Please provide both user and password in clear text separated by a ':', i.e. john:secret123 or jdoe:anonymous. Use TAB key to complete adding a user.]]></help>
<hint>Enter user:password here. Finish with TAB.</hint>
</field>
</subtab>
</tab>
<activetab>haproxy-general-settings</activetab>
</form>

View file

@ -0,0 +1,11 @@
<acl>
<!-- unique acl key, must be globally unique for all acl's -->
<page-services-haproxy>
<name>WebCfg - Services: HAProxy page</name>
<description>Allow access to the 'Services: HAProxy' page.</description>
<patterns>
<pattern>ui/haproxy/*</pattern>
<pattern>api/haproxy/*</pattern>
</patterns>
</page-services-haproxy>
</acl>

View file

@ -0,0 +1,83 @@
<?php
/**
* Copyright (C) 2016 Frank Wall
* Copyright (C) 2015 Deciso B.V.
*
* 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\HAProxy;
use OPNsense\Base\BaseModel;
/**
* Class HAProxy
* @package OPNsense\HAProxy
*/
class HAProxy extends BaseModel
{
/**
* retrieve frontend by number
* @param $frontendid frontend number
* @return null|BaseField frontend details
*/
public function getByFrontendID($frontendid)
{
foreach ($this->frontends->frontend->__items as $frontend) {
if ((string)$frontendid === (string)$frontend->frontendid) {
return $frontend;
}
}
return null;
}
/**
* retrieve backend by number
* @param $backendid frontend number
* @return null|BaseField backend details
*/
public function getByBackendID($backendid)
{
foreach ($this->backends->backend->__items as $backend) {
if ((string)$backendid === (string)$backend->backendid) {
return $backend;
}
}
return null;
}
/**
* check if module is enabled
* @return bool is the HAProxy enabled (1 or more active frontends)
*/
public function isEnabled()
{
foreach ($this->frontends->frontend->__items as $frontend) {
if ((string)$frontend->enabled == "1") {
return true;
}
}
return false;
}
}

View file

@ -0,0 +1,901 @@
<model>
<mount>//OPNsense/HAProxy</mount>
<description>
the HAProxy load balancer
</description>
<items>
<general>
<enabled type="BooleanField">
<default>0</default>
<Required>Y</Required>
</enabled>
<tuning>
<maxConnections type="IntegerField">
<MinimumValue>1</MinimumValue>
<MaximumValue>500000</MaximumValue>
<ValidationMessage>Please specify a value between 1 and 500000.</ValidationMessage>
<Required>N</Required>
</maxConnections>
<nbproc type="IntegerField">
<default>1</default>
<MinimumValue>1</MinimumValue>
<MaximumValue>128</MaximumValue>
<ValidationMessage>Please specify a value between 1 and 128.</ValidationMessage>
<Required>Y</Required>
</nbproc>
<maxDHSize type="IntegerField">
<default>1024</default>
<MinimumValue>1024</MinimumValue>
<MaximumValue>16384</MaximumValue>
<ValidationMessage>Please specify a value between 1024 and 16384.</ValidationMessage>
<Required>Y</Required>
</maxDHSize>
<bufferSize type="IntegerField">
<default>16384</default>
<MinimumValue>1024</MinimumValue>
<MaximumValue>1048576</MaximumValue>
<ValidationMessage>Please specify a value between 1024 and 1048576.</ValidationMessage>
<Required>N</Required>
</bufferSize>
<checkBufferSize type="IntegerField">
<default>16384</default>
<MinimumValue>1024</MinimumValue>
<MaximumValue>1048576</MaximumValue>
<ValidationMessage>Please specify a value between 1024 and 1048576.</ValidationMessage>
<Required>N</Required>
</checkBufferSize>
<spreadChecks type="IntegerField">
<default>0</default>
<MinimumValue>0</MinimumValue>
<MaximumValue>50</MaximumValue>
<ValidationMessage>Please specify a value between 0 and 50.</ValidationMessage>
<Required>Y</Required>
</spreadChecks>
<luaMaxMem type="IntegerField">
<default>0</default>
<MinimumValue>0</MinimumValue>
<MaximumValue>1024</MaximumValue>
<ValidationMessage>Please specify a value between 0 and 1024.</ValidationMessage>
<Required>N</Required>
</luaMaxMem>
<customOptions type="TextField">
<Required>N</Required>
</customOptions>
</tuning>
<defaults>
<maxConnections type="IntegerField">
<MinimumValue>1</MinimumValue>
<MaximumValue>500000</MaximumValue>
<ValidationMessage>Please specify a value between 1 and 500000.</ValidationMessage>
<Required>N</Required>
</maxConnections>
<timeoutClient type="IntegerField">
<MinimumValue>1</MinimumValue>
<MaximumValue>1000000</MaximumValue>
<default>30000</default>
<ValidationMessage>Please specify a value between 1 and 1000000.</ValidationMessage>
<Required>N</Required>
</timeoutClient>
<timeoutConnect type="IntegerField">
<MinimumValue>100</MinimumValue>
<MaximumValue>1000000</MaximumValue>
<default>30000</default>
<ValidationMessage>Please specify a value between 100 and 1000000.</ValidationMessage>
<Required>N</Required>
</timeoutConnect>
<timeoutServer type="IntegerField">
<MinimumValue>100</MinimumValue>
<MaximumValue>1000000</MaximumValue>
<default>30000</default>
<ValidationMessage>Please specify a value between 100 and 1000000.</ValidationMessage>
<Required>N</Required>
</timeoutServer>
<retries type="IntegerField">
<MinimumValue>1</MinimumValue>
<MaximumValue>100</MaximumValue>
<default>3</default>
<ValidationMessage>Please specify a value between 1 and 100.</ValidationMessage>
<Required>Y</Required>
</retries>
<redispatch type="OptionField">
<Required>N</Required>
<default>x-1</default>
<OptionValues>
<x3>redispatch on every 3rd retry</x3>
<x2>redispatch on every 2nd retry</x2>
<x1>redispatch on every retry</x1>
<x0>Disable redispatching</x0>
<x-1>redispatch on the last retry [default]</x-1>
<x-2>redispatch on the 2nd retry prior to the last retry</x-2>
<x-3>redispatch on the 3rd retry prior to the last retry</x-3>
</OptionValues>
</redispatch>
</defaults>
<logging>
<host type="TextField">
<default>127.0.0.1</default>
<mask>/^((([0-9a-zA-Z._\-\*]+:[0-9]+)([,]){0,1}))*/u</mask>
<ChangeCase>lower</ChangeCase>
<ValidationMessage>Please provide a valid host, i.e. 127.0.0.1 or 10.0.0.1:514.</ValidationMessage>
<Required>Y</Required>
</host>
<facility type="OptionField">
<Required>Y</Required>
<default>local0</default>
<OptionValues>
<alert>alert</alert>
<audit>audit</audit>
<auth2>auth2</auth2>
<auth>auth</auth>
<cron2>cron2</cron2>
<cron>cron</cron>
<daemon>daemon</daemon>
<ftp>ftp</ftp>
<kern>kern</kern>
<local0>local0</local0>
<local1>local1</local1>
<local2>local2</local2>
<local3>local3</local3>
<local4>local4</local4>
<local5>local5</local5>
<local6>local6</local6>
<local7>local7</local7>
<lpr>lpr</lpr>
<mail>mail</mail>
<news>news</news>
<ntp>ntp</ntp>
<syslog>syslog</syslog>
<user>user</user>
<uucp>uucp</uucp>
</OptionValues>
</facility>
<level type="OptionField">
<Required>N</Required>
<OptionValues>
<alert>alert</alert>
<crit>crit</crit>
<debug>debug</debug>
<emerg>emerg</emerg>
<err>err</err>
<info>info</info>
<notice>notice</notice>
<warning>warning</warning>
</OptionValues>
</level>
<length type="IntegerField">
<MinimumValue>64</MinimumValue>
<MaximumValue>65535</MaximumValue>
<ValidationMessage>Please specify a value between 64 and 65535.</ValidationMessage>
<Required>N</Required>
</length>
</logging>
<stats>
<enabled type="BooleanField">
<default>0</default>
<Required>N</Required>
</enabled>
<port type="IntegerField">
<MinimumValue>1024</MinimumValue>
<MaximumValue>65535</MaximumValue>
<default>8822</default>
<ValidationMessage>Please specify a value between 1024 and 65535.</ValidationMessage>
<Required>Y</Required>
</port>
<remoteEnabled type="BooleanField">
<default>0</default>
<Required>N</Required>
</remoteEnabled>
<!-- XXX: filter localhost and variations of it -->
<remoteBind type="CSVListField">
<Required>N</Required>
<multiple>Y</multiple>
<mask>/^((([0-9a-zA-Z._\-]+:[0-9a-zA-Z._\-]+)([,]){0,1}))*/u</mask>
<ChangeCase>lower</ChangeCase>
<ValidationMessage>Please provide a valid listen address, i.e. 10.0.0.1:8080 or haproxy.example.com:8999.</ValidationMessage>
</remoteBind>
<authEnabled type="BooleanField">
<default>0</default>
<Required>N</Required>
</authEnabled>
<users type="CSVListField">
<Required>N</Required>
<multiple>Y</multiple>
<mask>/^((([0-9a-zA-Z._\-]+:[0-9a-zA-Z._\-]+)([,]){0,1}))*/u</mask>
<ValidationMessage>Please provide a valid user and password, i.e. user:secret123.</ValidationMessage>
</users>
</stats>
</general>
<frontends>
<frontend type="ArrayField">
<id type="UniqueIdField">
<Required>N</Required>
</id>
<enabled type="BooleanField">
<default>1</default>
<Required>Y</Required>
</enabled>
<name type="TextField">
<Required>Y</Required>
<mask>/^([0-9a-zA-Z._]){1,255}$/u</mask>
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
</name>
<description type="TextField">
<Required>N</Required>
<mask>/^([\t\n\v\f\r 0-9a-zA-Z.:\-,_()\x{00A0}-\x{FFFF}]){1,255}$/u</mask>
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
</description>
<bind type="CSVListField">
<Required>Y</Required>
<multiple>Y</multiple>
<!-- <default>localhost:8080</default> -->
<mask>/^((([0-9a-zA-Z._\-\*]+:[0-9]+)([,]){0,1}))*/u</mask>
<ChangeCase>lower</ChangeCase>
<ValidationMessage>Please provide a valid listen address, i.e. 127.0.0.1:8080 or www.example.com:443.</ValidationMessage>
</bind>
<mode type="OptionField">
<Required>Y</Required>
<default>http</default>
<OptionValues>
<http>HTTP / HTTPS (SSL offloading) [default]</http>
<ssl>SSL / HTTPS (TCP mode)</ssl>
<tcp>TCP</tcp>
</OptionValues>
</mode>
<defaultBackend type="ModelRelationField">
<Model>
<template>
<source>OPNsense.HAProxy.HAProxy</source>
<items>backends.backend</items>
<display>name</display>
</template>
</Model>
<ValidationMessage>Related item not found</ValidationMessage>
<Required>N</Required>
</defaultBackend>
<!-- XXX: add tag <ssl> once nesting is supported by our framework -->
<ssl_enabled type="BooleanField">
<default>0</default>
<Required>Y</Required>
</ssl_enabled>
<ssl_certificates type="CertificateField">
<Required>N</Required>
<Multiple>Y</Multiple>
<ValidationMessage>Please select a valid certificate from the list.</ValidationMessage>
</ssl_certificates>
<ssl_customOptions type="TextField">
<Required>N</Required>
</ssl_customOptions>
<!-- XXX: add tag <tuning> once nesting is supported by our framework -->
<tuning_maxConnections type="IntegerField">
<MinimumValue>1</MinimumValue>
<MaximumValue>500000</MaximumValue>
<ValidationMessage>Please specify a value between 1 and 500000.</ValidationMessage>
<Required>N</Required>
</tuning_maxConnections>
<tuning_timeoutClient type="IntegerField">
<MinimumValue>1</MinimumValue>
<MaximumValue>1000000</MaximumValue>
<ValidationMessage>Please specify a value between 1 and 1000000.</ValidationMessage>
<Required>N</Required>
</tuning_timeoutClient>
<!-- XXX: add tag <logging> once nesting is supported by our framework -->
<logging_dontLogNull type="BooleanField">
<default>0</default>
<Required>Y</Required>
</logging_dontLogNull>
<logging_dontLogNormal type="BooleanField">
<default>0</default>
<Required>Y</Required>
</logging_dontLogNormal>
<logging_logSeparateErrors type="BooleanField">
<default>0</default>
<Required>Y</Required>
</logging_logSeparateErrors>
<logging_detailedLog type="BooleanField">
<default>0</default>
<Required>Y</Required>
</logging_detailedLog>
<logging_socketStats type="BooleanField">
<default>0</default>
<Required>Y</Required>
</logging_socketStats>
<forwardFor type="BooleanField">
<default>0</default>
<Required>Y</Required>
</forwardFor>
<connectionBehaviour type="OptionField">
<Required>Y</Required>
<default>http-keep-alive</default>
<OptionValues>
<http-keep-alive>http-keep-alive [default]</http-keep-alive>
<http-tunnel>http-tunnel</http-tunnel>
<httpclose>httpclose</httpclose>
<http-server-close>http-server-close</http-server-close>
<forceclose>forceclose</forceclose>
</OptionValues>
</connectionBehaviour>
<customOptions type="TextField">
<Required>N</Required>
</customOptions>
<linkedActions type="ModelRelationField">
<Model>
<template>
<source>OPNsense.HAProxy.HAProxy</source>
<items>actions.action</items>
<display>name</display>
</template>
</Model>
<ValidationMessage>Related item not found</ValidationMessage>
<multiple>Y</multiple>
<Required>N</Required>
</linkedActions>
<linkedErrorfiles type="ModelRelationField">
<Model>
<template>
<source>OPNsense.HAProxy.HAProxy</source>
<items>errorfiles.errorfile</items>
<display>name</display>
</template>
</Model>
<ValidationMessage>Related item not found</ValidationMessage>
<multiple>Y</multiple>
<Required>N</Required>
</linkedErrorfiles>
</frontend>
</frontends>
<backends>
<backend type="ArrayField">
<id type="UniqueIdField">
<Required>N</Required>
</id>
<enabled type="BooleanField">
<default>1</default>
<Required>Y</Required>
</enabled>
<name type="TextField">
<Required>Y</Required>
<mask>/^([0-9a-zA-Z._]){1,255}$/u</mask>
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
</name>
<description type="TextField">
<Required>N</Required>
<mask>/^([\t\n\v\f\r 0-9a-zA-Z.:\-,_()\x{00A0}-\x{FFFF}]){1,255}$/u</mask>
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
</description>
<mode type="OptionField">
<Required>Y</Required>
<default>http</default>
<OptionValues>
<http>HTTP (Layer 7) [default]</http>
<tcp>TCP (Layer 4)</tcp>
</OptionValues>
</mode>
<algorithm type="OptionField">
<Required>Y</Required>
<default>source</default>
<OptionValues>
<source>Source-IP Hash [default]</source>
<roundrobin>Round Robin</roundrobin>
<static-rr>Static Round Robin</static-rr>
<leastconn>Least Connections</leastconn>
<uri>URI Hash (only HTTP mode)</uri>
</OptionValues>
</algorithm>
<linkedServers type="ModelRelationField">
<Model>
<template>
<source>OPNsense.HAProxy.HAProxy</source>
<items>servers.server</items>
<display>name</display>
</template>
</Model>
<ValidationMessage>Related item not found</ValidationMessage>
<Multiple>Y</Multiple>
<Required>N</Required>
</linkedServers>
<healthCheckEnabled type="BooleanField">
<default>1</default>
<Required>Y</Required>
</healthCheckEnabled>
<healthCheck type="ModelRelationField">
<Model>
<template>
<source>OPNsense.HAProxy.HAProxy</source>
<items>healthchecks.healthcheck</items>
<display>name</display>
</template>
</Model>
<ValidationMessage>Related item not found</ValidationMessage>
<Multiple>N</Multiple>
<Required>N</Required>
</healthCheck>
<healthCheckLogStatus type="BooleanField">
<default>0</default>
<Required>N</Required>
</healthCheckLogStatus>
<!-- XXX: add tag <stickiness> once nesting is supported by our framework -->
<stickiness_pattern type="OptionField">
<Required>N</Required>
<default>sourceipv4</default>
<OptionValues>
<sourceipv4>Stick on Source-IP [default]</sourceipv4>
<sourceipv6>Stick on Source-IPv6</sourceipv6>
<cookievalue>Stick on existing Cookie value</cookievalue>
<rdpcookie>Stick on RDP-Cookie</rdpcookie>
</OptionValues>
</stickiness_pattern>
<stickiness_expire type="TextField">
<Required>Y</Required>
<default>30m</default>
<mask>/^([0-9]{1,5}[d|h|m|s|ms]{1})*/u</mask>
<ChangeCase>lower</ChangeCase>
<ValidationMessage>Should be a number between 1 and 5 characters followed by either "d", "h", "m", "s" or "ms".</ValidationMessage>
</stickiness_expire>
<stickiness_size type="TextField">
<Required>Y</Required>
<default>50k</default>
<mask>/^([0-9]{1,5}[k|m|g]{1})*/u</mask>
<ChangeCase>lower</ChangeCase>
<ValidationMessage>Should be a number between 1 and 5 characters followed by either "k", "m" or "g".</ValidationMessage>
</stickiness_size>
<stickiness_cookiename type="TextField">
<Required>N</Required>
<mask>/^([0-9a-zA-Z\.,_\-:]){1,1024}$/u</mask>
<ValidationMessage>Does not look like a valid cookie name (most special characters are not allowed).</ValidationMessage>
</stickiness_cookiename>
<stickiness_cookielength type="IntegerField">
<MinimumValue>1</MinimumValue>
<MaximumValue>10000</MaximumValue>
<ValidationMessage>Please specify a value between 1 and 10000.</ValidationMessage>
<Required>N</Required>
</stickiness_cookielength>
<!-- XXX: add tag <tuning> once nesting is supported by our framework -->
<tuning_timeoutConnect type="IntegerField">
<MinimumValue>100</MinimumValue>
<MaximumValue>1000000</MaximumValue>
<ValidationMessage>Please specify a value between 100 and 1000000.</ValidationMessage>
<Required>N</Required>
</tuning_timeoutConnect>
<tuning_timeoutServer type="IntegerField">
<MinimumValue>100</MinimumValue>
<MaximumValue>1000000</MaximumValue>
<ValidationMessage>Please specify a value between 100 and 1000000.</ValidationMessage>
<Required>N</Required>
</tuning_timeoutServer>
<tuning_retries type="IntegerField">
<MinimumValue>1</MinimumValue>
<MaximumValue>100</MaximumValue>
<ValidationMessage>Please specify a value between 1 and 100.</ValidationMessage>
<Required>N</Required>
</tuning_retries>
<customOptions type="TextField">
<Required>N</Required>
</customOptions>
<linkedActions type="ModelRelationField">
<Model>
<template>
<source>OPNsense.HAProxy.HAProxy</source>
<items>actions.action</items>
<display>name</display>
</template>
</Model>
<ValidationMessage>Related item not found</ValidationMessage>
<multiple>Y</multiple>
<Required>N</Required>
</linkedActions>
<linkedErrorfiles type="ModelRelationField">
<Model>
<template>
<source>OPNsense.HAProxy.HAProxy</source>
<items>errorfiles.errorfile</items>
<display>name</display>
</template>
</Model>
<ValidationMessage>Related item not found</ValidationMessage>
<multiple>Y</multiple>
<Required>N</Required>
</linkedErrorfiles>
</backend>
</backends>
<servers>
<server type="ArrayField">
<name type="TextField">
<mask>/^([0-9a-zA-Z._]){1,255}$/u</mask>
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
<Required>Y</Required>
</name>
<description type="TextField">
<mask>/^([\t\n\v\f\r 0-9a-zA-Z.:\-,_()\x{00A0}-\x{FFFF}]){1,255}$/u</mask>
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
<Required>N</Required>
</description>
<address type="TextField">
<mask>/^([0-9a-zA-Z\.,_\-:]){0,1024}$/u</mask>
<ValidationMessage>Please specify a valid servername or IP address.</ValidationMessage>
<Required>Y</Required>
</address>
<port type="IntegerField">
<default>80</default>
<MinimumValue>1</MinimumValue>
<MaximumValue>65535</MaximumValue>
<ValidationMessage>Please specify a value between 1 and 65535.</ValidationMessage>
<Required>Y</Required>
</port>
<mode type="OptionField">
<Required>Y</Required>
<default>active</default>
<OptionValues>
<active>active [default]</active>
<backup>backup</backup>
<disabled>disabled</disabled>
<inactive>inactive</inactive>
</OptionValues>
</mode>
<ssl type="BooleanField">
<default>0</default>
<Required>Y</Required>
</ssl>
<weight type="IntegerField">
<MinimumValue>0</MinimumValue>
<MaximumValue>256</MaximumValue>
<ValidationMessage>Please specify a value between 0 and 256.</ValidationMessage>
<Required>N</Required>
</weight>
<checkInterval type="IntegerField">
<default>1000</default>
<MinimumValue>500</MinimumValue>
<MaximumValue>100000</MaximumValue>
<ValidationMessage>Please specify a value between 500 and 100000.</ValidationMessage>
<Required>Y</Required>
</checkInterval>
</server>
</servers>
<healthchecks>
<healthcheck type="ArrayField">
<name type="TextField">
<mask>/^([0-9a-zA-Z._]){1,255}$/u</mask>
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
<Required>Y</Required>
</name>
<description type="TextField">
<mask>/^([\t\n\v\f\r 0-9a-zA-Z.:\-,_()\x{00A0}-\x{FFFF}]){1,255}$/u</mask>
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
<Required>N</Required>
</description>
<type type="OptionField">
<Required>Y</Required>
<default>http</default>
<OptionValues>
<tcp>TCP</tcp>
<http>HTTP [default]</http>
<agent>Agent</agent>
<ldap>LDAP</ldap>
<mysql>MySQL</mysql>
<pgsql>PostgreSQL</pgsql>
<redis>Redis</redis>
<smtp>SMTP</smtp>
<esmtp>ESMTP</esmtp>
<ssl>SSL</ssl>
</OptionValues>
</type>
<interval type="IntegerField">
<MinimumValue>250</MinimumValue>
<MaximumValue>1000000</MaximumValue>
<default>1000</default>
<ValidationMessage>Please specify a value between 250 and 1000000.</ValidationMessage>
<Required>N</Required>
</interval>
<!-- XXX: add tag <http> once nesting is supported by our framework -->
<http_method type="OptionField">
<Required>N</Required>
<default>options</default>
<OptionValues>
<options>OPTIONS [default]</options>
<head>HEAD</head>
<get>GET</get>
<put>PUT</put>
<post>POST</post>
<delete>DELETE</delete>
<trace>TRACE</trace>
</OptionValues>
</http_method>
<http_uri type="TextField">
<default>/</default>
<mask>/^(.*){1,255}$/u</mask>
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
<Required>N</Required>
</http_uri>
<http_version type="OptionField">
<Required>N</Required>
<default>http10</default>
<OptionValues>
<http10>HTTP/1.0 [default]</http10>
<http11>HTTP/1.1</http11>
</OptionValues>
</http_version>
<http_host type="TextField">
<default>localhost</default>
<mask>/^([0-9a-zA-Z._\-]){1,255}$/u</mask>
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
<Required>N</Required>
</http_host>
<http_expressionEnabled type="BooleanField">
<default>0</default>
<Required>N</Required>
</http_expressionEnabled>
<http_expression type="OptionField">
<Required>N</Required>
<OptionValues>
<status>test the exact string match for the HTTP status code</status>
<rstatus>test a regular expression for the HTTP status code</rstatus>
<string>test the exact string match in the HTTP response body</string>
<rstring>test a regular expression on the HTTP response body</rstring>
</OptionValues>
</http_expression>
<http_negate type="BooleanField">
<default>0</default>
<Required>N</Required>
</http_negate>
<http_value type="TextField">
<Required>N</Required>
</http_value>
<agentPort type="IntegerField">
<MinimumValue>1</MinimumValue>
<MaximumValue>65535</MaximumValue>
<ValidationMessage>Please specify a value between 1 and 65535.</ValidationMessage>
<Required>N</Required>
</agentPort>
<dbUser type="TextField">
<mask>/^([0-9a-zA-Z._\-]){1,255}$/u</mask>
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
<Required>N</Required>
</dbUser>
<smtpDomain type="TextField">
<mask>/^([0-9a-zA-Z._\-]){1,255}$/u</mask>
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
<Required>N</Required>
</smtpDomain>
</healthcheck>
</healthchecks>
<acls>
<acl type="ArrayField">
<id type="UniqueIdField">
<Required>N</Required>
</id>
<name type="TextField">
<mask>/^([0-9a-zA-Z._]){1,255}$/u</mask>
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
<Required>Y</Required>
</name>
<description type="TextField">
<mask>/^([\t\n\v\f\r 0-9a-zA-Z.:\-,_()\x{00A0}-\x{FFFF}]){1,255}$/u</mask>
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
<Required>N</Required>
</description>
<expression type="OptionField">
<Required>Y</Required>
<OptionValues>
<host_starts_with>Host starts with</host_starts_with>
<host_ends_with>Host ends with</host_ends_with>
<host_matches>Host matches</host_matches>
<host_regex>Host regex</host_regex>
<host_contains>Host contains</host_contains>
<path_starts_with>Path starts with</path_starts_with>
<path_ends_with>Path ends with</path_ends_with>
<path_matches>Path matches</path_matches>
<path_regex>Path regex</path_regex>
<path_contains>Path contains</path_contains>
<url_parameter>URL parameter contains</url_parameter>
<ssl_c_verify_code>SSL Client certificate verify error result</ssl_c_verify_code>
<ssl_c_verify>SSL Client certificate is valid</ssl_c_verify>
<ssl_c_ca_commonname>SSL Client issued by CA common-name</ssl_c_ca_commonname>
<source_ip>Source IP matches IP or Alias</source_ip>
<backendservercount>Minimum count usable servers</backendservercount>
<traffic_is_http>Traffic is http (no value needed)</traffic_is_http>
<traffic_is_ssl>Traffic is ssl (no value needed)</traffic_is_ssl>
<ssl_sni_matches>SNI TLS extension matches</ssl_sni_matches>
<ssl_sni_contains>SNI TLS extension contains</ssl_sni_contains>
<ssl_sni_starts_with>SNI TLS extension starts with</ssl_sni_starts_with>
<ssl_sni_ends_with>SNI TLS extension ends with</ssl_sni_ends_with>
<ssl_sni_regex>SNI TLS extension regex</ssl_sni_regex>
<custom_acl>Custom ACL</custom_acl>
</OptionValues>
</expression>
<negate type="BooleanField">
<default>0</default>
<Required>Y</Required>
</negate>
<value type="TextField">
<Required>N</Required>
</value>
<urlparam type="TextField">
<Required>N</Required>
</urlparam>
<queryBackend type="ModelRelationField">
<Model>
<template>
<source>OPNsense.HAProxy.HAProxy</source>
<items>backends.backend</items>
<display>name</display>
</template>
</Model>
<ValidationMessage>Related item not found</ValidationMessage>
<Required>N</Required>
</queryBackend>
</acl>
</acls>
<actions>
<action type="ArrayField">
<name type="TextField">
<mask>/^([0-9a-zA-Z._]){1,255}$/u</mask>
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
<Required>Y</Required>
</name>
<description type="TextField">
<mask>/^([\t\n\v\f\r 0-9a-zA-Z.:\-,_()\x{00A0}-\x{FFFF}]){1,255}$/u</mask>
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
<Required>N</Required>
</description>
<testType type="OptionField">
<Required>Y</Required>
<default>if</default>
<OptionValues>
<if>IF [default]</if>
<unless>UNLESS</unless>
</OptionValues>
</testType>
<linkedAcls type="ModelRelationField">
<Model>
<template>
<source>OPNsense.HAProxy.HAProxy</source>
<items>acls.acl</items>
<display>name</display>
</template>
</Model>
<ValidationMessage>Related item not found</ValidationMessage>
<Multiple>Y</Multiple>
<Required>N</Required>
</linkedAcls>
<operator type="OptionField">
<Required>N</Required>
<default>and</default>
<OptionValues>
<and>AND [default]</and>
<or>OR</or>
</OptionValues>
</operator>
<type type="OptionField">
<Required>Y</Required>
<OptionValues>
<use_backend>Use Backend</use_backend>
<use_server>Use Server</use_server>
<http-request_allow>http-request allow</http-request_allow>
<http-request_deny>http-request deny</http-request_deny>
<http-request_tarpit>http-request tarpit</http-request_tarpit>
<http-request_auth>http-request auth</http-request_auth>
<http-request_redirect>http-request redirect</http-request_redirect>
<http-request_lua>http-request lua action</http-request_lua>
<http-request_use-service>http-request lua service</http-request_use-service>
<http-request_add-header>http-request header add</http-request_add-header>
<http-request_set-header>http-request header set</http-request_set-header>
<http-request_del-header>http-request header delete</http-request_del-header>
<http-request_replace-header>http-request header replace</http-request_replace-header>
<http-request_replace-value>http-request header replace value</http-request_replace-value>
<http-response_allow>http-response allow</http-response_allow>
<http-response_deny>http-response deny</http-response_deny>
<http-response_lua>http-response lua script</http-response_lua>
<http-response_add-header>http-response header add</http-response_add-header>
<http-response_set-header>http-response header set</http-response_set-header>
<http-response_del-header>http-response header delete</http-response_del-header>
<http-response_replace-header>http-response header replace</http-response_replace-header>
<http-response_replace-value>http-response header replace value</http-response_replace-value>
<tcp-request_connection_accept>tcp-request connection accept</tcp-request_connection_accept>
<tcp-request_connection_reject>tcp-request connection reject</tcp-request_connection_reject>
<tcp-request_content_accept>tcp-request content accept</tcp-request_content_accept>
<tcp-request_content_reject>tcp-request content reject</tcp-request_content_reject>
<tcp-request_content_lua>tcp-request content lua script</tcp-request_content_lua>
<tcp-request_content_use-service>tcp-request content use-service</tcp-request_content_use-service>
<tcp-response_content_accept>tcp-response content accept</tcp-response_content_accept>
<tcp-response_content_close>tcp-response content close</tcp-response_content_close>
<tcp-response_content_reject>tcp-response content reject</tcp-response_content_reject>
<tcp-response_content_lua>tcp-response content lua script</tcp-response_content_lua>
<custom>Custom</custom>
</OptionValues>
</type>
<useBackend type="ModelRelationField">
<Model>
<template>
<source>OPNsense.HAProxy.HAProxy</source>
<items>backends.backend</items>
<display>name</display>
</template>
</Model>
<ValidationMessage>Related item not found</ValidationMessage>
<Multiple>Y</Multiple>
<Required>N</Required>
</useBackend>
<useServer type="ModelRelationField">
<Model>
<template>
<source>OPNsense.HAProxy.HAProxy</source>
<items>servers.server</items>
<display>name</display>
</template>
</Model>
<ValidationMessage>Related item not found</ValidationMessage>
<Multiple>Y</Multiple>
<Required>N</Required>
</useServer>
<actionName type="TextField">
<Required>N</Required>
</actionName>
<actionFind type="TextField">
<Required>N</Required>
</actionFind>
<actionValue type="TextField">
<Required>N</Required>
</actionValue>
</action>
</actions>
<luas>
<lua type="ArrayField">
<id type="UniqueIdField">
<Required>Y</Required>
</id>
<enabled type="BooleanField">
<default>1</default>
<Required>Y</Required>
</enabled>
<name type="TextField">
<mask>/^([0-9a-zA-Z_\-]){1,255}$/u</mask>
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
<Required>Y</Required>
</name>
<description type="TextField">
<mask>/^([\t\n\v\f\r 0-9a-zA-Z.:\-,_()\x{00A0}-\x{FFFF}]){1,255}$/u</mask>
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
<Required>N</Required>
</description>
<content type="TextField">
<Required>Y</Required>
</content>
</lua>
</luas>
<errorfiles>
<errorfile type="ArrayField">
<id type="UniqueIdField">
<Required>Y</Required>
</id>
<name type="TextField">
<mask>/^([0-9a-zA-Z_\-]){1,255}$/u</mask>
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
<Required>Y</Required>
</name>
<description type="TextField">
<mask>/^([\t\n\v\f\r 0-9a-zA-Z.:\-,_()\x{00A0}-\x{FFFF}]){1,255}$/u</mask>
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
<Required>N</Required>
</description>
<code type="OptionField">
<Required>Y</Required>
<default>503</default>
<OptionValues>
<x200>200</x200>
<x400>400</x400>
<x403>403</x403>
<x405>405</x405>
<x408>408</x408>
<x429>429</x429>
<x500>500</x500>
<x502>502</x502>
<x503>503</x503>
<x504>504</x504>
</OptionValues>
</code>
<content type="TextField">
<Required>Y</Required>
</content>
</errorfile>
</errorfiles>
</items>
</model>

View file

@ -0,0 +1,28 @@
<menu>
<Services>
<HAProxy VisibleName="HAProxy" cssClass="fa fa-truck fa-fw">
<Settings order="10" url="/ui/haproxy/">
<GeneralSettings VisibleName="Service Settings" url="/ui/haproxy/#subtab_haproxy-general-settings"/>
<GlobalParameters VisibleName="Global Parameters" url="/ui/haproxy/#subtab_haproxy-general-global"/>
<DefaultParameters VisibleName="Detault Parameters" url="/ui/haproxy/#subtab_haproxy-general-defaults"/>
<LoggingConfiguration VisibleName="Logging Configuration" url="/ui/haproxy/#subtab_haproxy-general-logging"/>
<StatisticsConfiguration VisibleName="Statistics Configuration" url="/ui/haproxy/#subtab_haproxy-general-statistics"/>
<Frontends VisibleName="Frontends" url="/ui/haproxy/#frontends"/>
<Backends VisibleName="Backends" url="/ui/haproxy/#backends"/>
<Servers VisibleName="Servers" url="/ui/haproxy/#servers"/>
<HealthChecks VisibleName="Health Checks" url="/ui/haproxy/#healthchecks"/>
<Actions VisibleName="Actions" url="/ui/haproxy/#actions"/>
<Acls VisibleName="ACLs" url="/ui/haproxy/#acls"/>
<Luas VisibleName="Lua Scripts" url="/ui/haproxy/#luas"/>
<Errorfiles VisibleName="Error Files" url="/ui/haproxy/#errorfiles"/>
</Settings>
<Statistics order="20" url="/ui/haproxy/statistics/">
<Info VisibleName="Info" url="/ui/haproxy/statistics/#info"/>
<Status VisibleName="Status" url="/ui/haproxy/statistics/#status"/>
<Counters VisibleName="Counters" url="/ui/haproxy/statistics/#counters"/>
<StickTables VisibleName="Stick Tables" url="/ui/haproxy/statistics/#tables"/>
</Statistics>
<!-- <Log VisibleName="Log File" order="30" url="/diag_logs_haproxy.php"/> -->
</HAProxy>
</Services>
</menu>

View file

@ -0,0 +1,657 @@
{#
Copyright (C) 2016 Frank Wall
OPNsense® is Copyright © 2014 2015 by Deciso B.V.
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.
#}
<script type="text/javascript">
$( document ).ready(function() {
var data_get_map = {'frm_haproxy':"/api/haproxy/settings/get"};
// load initial data
mapDataToFormUI(data_get_map).done(function(){
formatTokenizersUI();
$('.selectpicker').selectpicker('refresh');
// request service status on load and update status box
ajaxCall(url="/api/haproxy/service/status", sendData={}, callback=function(data,status) {
updateServiceStatusUI(data['status']);
});
});
/***********************************************************************
* link grid actions
**********************************************************************/
$("#grid-frontends").UIBootgrid(
{ search:'/api/haproxy/settings/searchFrontends',
get:'/api/haproxy/settings/getFrontend/',
set:'/api/haproxy/settings/setFrontend/',
add:'/api/haproxy/settings/addFrontend/',
del:'/api/haproxy/settings/delFrontend/',
toggle:'/api/haproxy/settings/toggleFrontend/',
options: {
rowCount:[10,25,50,100,500,1000]
}
}
);
$("#grid-backends").UIBootgrid(
{ search:'/api/haproxy/settings/searchBackends',
get:'/api/haproxy/settings/getBackend/',
set:'/api/haproxy/settings/setBackend/',
add:'/api/haproxy/settings/addBackend/',
del:'/api/haproxy/settings/delBackend/',
toggle:'/api/haproxy/settings/toggleBackend/',
options: {
rowCount:[10,25,50,100,500,1000]
}
}
);
$("#grid-servers").UIBootgrid(
{ search:'/api/haproxy/settings/searchServers',
get:'/api/haproxy/settings/getServer/',
set:'/api/haproxy/settings/setServer/',
add:'/api/haproxy/settings/addServer/',
del:'/api/haproxy/settings/delServer/',
options: {
rowCount:[10,25,50,100,500,1000]
}
}
);
$("#grid-healthchecks").UIBootgrid(
{ search:'/api/haproxy/settings/searchHealthchecks',
get:'/api/haproxy/settings/getHealthcheck/',
set:'/api/haproxy/settings/setHealthcheck/',
add:'/api/haproxy/settings/addHealthcheck/',
del:'/api/haproxy/settings/delHealthcheck/',
options: {
rowCount:[10,25,50,100,500,1000]
}
}
);
$("#grid-actions").UIBootgrid(
{ search:'/api/haproxy/settings/searchActions',
get:'/api/haproxy/settings/getAction/',
set:'/api/haproxy/settings/setAction/',
add:'/api/haproxy/settings/addAction/',
del:'/api/haproxy/settings/delAction/',
options: {
rowCount:[10,25,50,100,500,1000]
}
}
);
$("#grid-acls").UIBootgrid(
{ search:'/api/haproxy/settings/searchAcls',
get:'/api/haproxy/settings/getAcl/',
set:'/api/haproxy/settings/setAcl/',
add:'/api/haproxy/settings/addAcl/',
del:'/api/haproxy/settings/delAcl/',
options: {
rowCount:[10,25,50,100,500,1000]
}
}
);
$("#grid-luas").UIBootgrid(
{ search:'/api/haproxy/settings/searchLuas',
get:'/api/haproxy/settings/getLua/',
set:'/api/haproxy/settings/setLua/',
add:'/api/haproxy/settings/addLua/',
del:'/api/haproxy/settings/delLua/',
toggle:'/api/haproxy/settings/toggleLua/',
options: {
rowCount:[10,25,50,100,500,1000]
}
}
);
$("#grid-errorfiles").UIBootgrid(
{ search:'/api/haproxy/settings/searchErrorfiles',
get:'/api/haproxy/settings/getErrorfile/',
set:'/api/haproxy/settings/setErrorfile/',
add:'/api/haproxy/settings/addErrorfile/',
del:'/api/haproxy/settings/delErrorfile/',
options: {
rowCount:[10,25,50,100,500,1000]
}
}
);
/***********************************************************************
* Commands
**********************************************************************/
// Reconfigure haproxy - activate changes
$('[id*="reconfigureAct"]').each(function(){
$(this).click(function(){
// set progress animation
$('[id*="reconfigureAct_progress"]').each(function(){
$(this).addClass("fa fa-spinner fa-pulse");
});
// first run syntax check to catch critical errors
ajaxCall(url="/api/haproxy/service/configtest", sendData={}, callback=function(data,status) {
// show warning in case of critical errors
if (data['result'].indexOf('ALERT') > -1) {
BootstrapDialog.show({
type: BootstrapDialog.TYPE_DANGER,
title: "{{ lang._('HAProxy config contains critical errors') }}",
message: "{{ lang._('The HAProxy service may not be able to start due to critical errors. Try anyway?') }}",
buttons: [{
label: '{{ lang._('Continue') }}',
cssClass: 'btn-primary',
action: function(dlg){
ajaxCall(url="/api/haproxy/service/reconfigure", sendData={}, callback=function(data,status) {
if (status != "success" || data['status'] != 'ok') {
BootstrapDialog.show({
type: BootstrapDialog.TYPE_WARNING,
title: "{{ lang._('Error reconfiguring HAProxy') }}",
message: data['status'],
draggable: true
});
}
});
// when done, disable progress animation
$('[id*="reconfigureAct_progress"]').each(function(){
$(this).removeClass("fa fa-spinner fa-pulse");
});
dlg.close();
}
}, {
icon: 'fa fa-trash-o',
label: '{{ lang._('Abort') }}',
action: function(dlg){
// when done, disable progress animation
$('[id*="reconfigureAct_progress"]').each(function(){
$(this).removeClass("fa fa-spinner fa-pulse");
});
dlg.close();
}
}]
});
} else {
ajaxCall(url="/api/haproxy/service/reconfigure", sendData={}, callback=function(data,status) {
if (status != "success" || data['status'] != 'ok') {
BootstrapDialog.show({
type: BootstrapDialog.TYPE_WARNING,
title: "{{ lang._('Error reconfiguring HAProxy') }}",
message: data['status'],
draggable: true
});
}
// when done, disable progress animation
$('[id*="reconfigureAct_progress"]').each(function(){
$(this).removeClass("fa fa-spinner fa-pulse");
});
});
}
});
});
});
// Test configuration file
$('[id*="configtestAct"]').each(function(){
$(this).click(function(){
// set progress animation
$('[id*="configtestAct_progress"]').each(function(){
$(this).addClass("fa fa-spinner fa-pulse");
});
ajaxCall(url="/api/haproxy/service/configtest", sendData={}, callback=function(data,status) {
// when done, disable progress animation
$('[id*="configtestAct_progress"]').each(function(){
$(this).removeClass("fa fa-spinner fa-pulse");
});
if (data['result'].indexOf('ALERT') > -1) {
BootstrapDialog.show({
type: BootstrapDialog.TYPE_DANGER,
title: "{{ lang._('HAProxy config contains critical errors') }}",
message: data['result'],
draggable: true
});
} else if (data['result'].indexOf('WARNING') > -1) {
BootstrapDialog.show({
type: BootstrapDialog.TYPE_WARNING,
title: "{{ lang._('HAProxy config contains minor errors') }}",
message: data['result'],
draggable: true
});
} else {
BootstrapDialog.show({
type: BootstrapDialog.TYPE_WARNING,
title: "{{ lang._('HAProxy config test result') }}",
message: "{{ lang._('Your HAProxy config contains no errors.') }}",
draggable: true
});
}
});
});
});
// form save event handlers for all defined forms
$('[id*="save_"]').each(function(){
$(this).click(function(){
var frm_id = $(this).closest("form").attr("id");
var frm_title = $(this).closest("form").attr("data-title");
// save data for tab
saveFormToEndpoint(url="/api/haproxy/settings/set",formid=frm_id,callback_ok=function(){
// set progress animation when reloading
$("#"+frm_id+"_progress").addClass("fa fa-spinner fa-pulse");
// on correct save, perform reconfigure
ajaxCall(url="/api/haproxy/service/reconfigure", sendData={}, callback=function(data,status){
// when done, disable progress animation.
$("#"+frm_id+"_progress").removeClass("fa fa-spinner fa-pulse");
if (status != "success" || data['status'] != 'ok' ) {
// fix error handling
BootstrapDialog.show({
type:BootstrapDialog.TYPE_WARNING,
title: frm_title,
message: JSON.stringify(data),
draggable: true
});
} else {
// request service status after successful save and update status box (wait a few seconds before update)
setTimeout(function(){
ajaxCall(url="/api/haproxy/service/status", sendData={}, callback=function(data,status) {
updateServiceStatusUI(data['status']);
});
},3000);
}
});
});
});
});
// 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);
});
});
</script>
<ul class="nav nav-tabs" role="tablist" id="maintabs">
{% for tab in mainForm['tabs']|default([]) %}
{% if tab['subtabs']|default(false) %}
{# Tab with dropdown #}
{# Find active subtab #}
{% set active_subtab="" %}
{% for subtab in tab['subtabs']|default({}) %}
{% if subtab[0]==mainForm['activetab']|default("") %}
{% set active_subtab=subtab[0] %}
{% endif %}
{% endfor %}
<li role="presentation" class="dropdown {% if mainForm['activetab']|default("") == active_subtab %}active{% endif %}">
<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" style="border-left: 1px dashed lightgray;">
<b><span class="caret"></span></b>
</a>
<a data-toggle="tab" href="#subtab_{{tab['subtabs'][0][0]}}" class="visible-lg-inline-block visible-md-inline-block visible-xs-inline-block visible-sm-inline-block" style="border-right:0px;"><b>{{tab[1]}}</b></a>
<ul class="dropdown-menu" role="menu">
{% for subtab in tab['subtabs']|default({})%}
<li class="{% if mainForm['activetab']|default("") == subtab[0] %}active{% endif %}"><a data-toggle="tab" href="#subtab_{{subtab[0]}}"><i class="fa fa-check-square"></i> {{subtab[1]}}</a></li>
{% endfor %}
</ul>
</li>
{% else %}
{# Standard Tab #}
<li {% if mainForm['activetab']|default("") == tab[0] %} class="active" {% endif %}>
<a data-toggle="tab" href="#tab_{{tab[0]}}">
<b>{{tab[1]}}</b>
</a>
</li>
{% endif %}
{% endfor %}
{# add custom content #}
<li><a data-toggle="tab" href="#frontends"><b>{{ lang._('Frontends') }}</b></a></li>
<li><a data-toggle="tab" href="#backends"><b>{{ lang._('Backends') }}</b></a></li>
<li><a data-toggle="tab" href="#servers"><b>{{ lang._('Servers') }}</b></a></li>
<li><a data-toggle="tab" href="#healthchecks"><b>{{ lang._('Health Checks') }}</b></a></li>
<li><a data-toggle="tab" href="#actions"><b>{{ lang._('Actions') }}</b></a></li>
<li><a data-toggle="tab" href="#acls"><b>{{ lang._('ACLs') }}</b></a></li>
<li><a data-toggle="tab" href="#luas"><b>{{ lang._('Lua Scripts') }}</b></a></li>
<li><a data-toggle="tab" href="#errorfiles"><b>{{ lang._('Error Files') }}</b></a></li>
</ul>
<div class="content-box tab-content">
{% for tab in mainForm['tabs']|default([]) %}
{% if tab['subtabs']|default(false) %}
{# Tab with dropdown #}
{% for subtab in tab['subtabs']|default({})%}
<div id="subtab_{{subtab[0]}}" class="tab-pane fade{% if mainForm['activetab']|default("") == subtab[0] %} in active {% endif %}">
{{ partial("layout_partials/base_form",['fields':subtab[2],'id':'frm_'~subtab[0],'data_title':subtab[1],'apply_btn_id':'save_'~subtab[0]])}}
</div>
{% endfor %}
{% endif %}
{% if tab['subtabs']|default(false)==false %}
<div id="tab_{{tab[0]}}" class="tab-pane fade{% if mainForm['activetab']|default("") == tab[0] %} in active {% endif %}">
{{ partial("layout_partials/base_form",['fields':tab[2],'id':'frm_'~tab[0],'apply_btn_id':'save_'~tab[0]])}}
</div>
{% endif %}
{% endfor %}
<div id="frontends" class="tab-pane fade">
<!-- tab page "frontends" -->
<table id="grid-frontends" class="table table-condensed table-hover table-striped table-responsive" data-editDialog="DialogFrontend">
<thead>
<tr>
<th data-column-id="enabled" data-width="6em" data-type="string" data-formatter="rowtoggle">{{ lang._('Enabled') }}</th>
<th data-column-id="frontendid" data-type="number" data-visible="false">{{ lang._('Frontend ID') }}</th>
<th data-column-id="name" data-type="string">{{ lang._('Frontend Name') }}</th>
<th data-column-id="description" data-type="string">{{ lang._('Description') }}</th>
<th data-column-id="commands" data-width="7em" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
<th data-column-id="uuid" data-type="string" data-identifier="true" data-visible="false">{{ lang._('ID') }}</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 data-action="deleteSelected" type="button" class="btn btn-xs btn-default"><span class="fa fa-trash-o"></span></button>
</td>
</tr>
</tfoot>
</table>
<!-- apply button -->
<div class="col-md-12">
<hr/>
<button class="btn btn-primary" id="reconfigureAct-frontends" type="button"><b>{{ lang._('Apply') }}</b><i id="reconfigureAct_progress" class=""></i></button>
<button class="btn btn-primary" id="configtestAct-frontends" type="button"><b>{{ lang._('Test syntax') }}</b><i id="configtestAct_progress" class=""></i></button>
<br/>
<br/>
</div>
</div>
<div id="backends" class="tab-pane fade">
<!-- tab page "backends" -->
<table id="grid-backends" class="table table-condensed table-hover table-striped table-responsive" data-editDialog="DialogBackend">
<thead>
<tr>
<th data-column-id="enabled" data-width="6em" data-type="string" data-formatter="rowtoggle">{{ lang._('Enabled') }}</th>
<th data-column-id="backendid" data-type="number" data-visible="false">{{ lang._('Backend ID') }}</th>
<th data-column-id="name" data-type="string">{{ lang._('Backend Name') }}</th>
<th data-column-id="description" data-type="string">{{ lang._('Description') }}</th>
<th data-column-id="commands" data-width="7em" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
<th data-column-id="uuid" data-type="string" data-identifier="true" data-visible="false">{{ lang._('ID') }}</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 data-action="deleteSelected" type="button" class="btn btn-xs btn-default"><span class="fa fa-trash-o"></span></button>
</td>
</tr>
</tfoot>
</table>
<!-- apply button -->
<div class="col-md-12">
<hr/>
<button class="btn btn-primary" id="reconfigureAct-backends" type="button"><b>{{ lang._('Apply') }}</b><i id="reconfigureAct_progress" class=""></i></button>
<button class="btn btn-primary" id="configtestAct-backends" type="button"><b>{{ lang._('Test syntax') }}</b><i id="configtestAct_progress" class=""></i></button>
<br/>
<br/>
</div>
</div>
<div id="servers" class="tab-pane fade">
<!-- tab page "servers" -->
<table id="grid-servers" class="table table-condensed table-hover table-striped table-responsive" data-editDialog="DialogServer">
<thead>
<tr>
<th data-column-id="serverid" data-type="number" data-visible="false">{{ lang._('Server id') }}</th>
<th data-column-id="name" data-type="string">{{ lang._('Server Name') }}</th>
<th data-column-id="address" data-type="string">{{ lang._('Server Address') }}</th>
<th data-column-id="port" data-type="string">{{ lang._('Server Port') }}</th>
<th data-column-id="description" data-type="string">{{ lang._('Description') }}</th>
<th data-column-id="commands" data-width="7em" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
<th data-column-id="uuid" data-type="string" data-identifier="true" data-visible="false">{{ lang._('ID') }}</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 data-action="deleteSelected" type="button" class="btn btn-xs btn-default"><span class="fa fa-trash-o"></span></button>
</td>
</tr>
</tfoot>
</table>
<!-- apply button -->
<div class="col-md-12">
<hr/>
<button class="btn btn-primary" id="reconfigureAct-servers" type="button"><b>{{ lang._('Apply') }}</b><i id="reconfigureAct_progress" class=""></i></button>
<button class="btn btn-primary" id="configtestAct-servers" type="button"><b>{{ lang._('Test syntax') }}</b><i id="configtestAct_progress" class=""></i></button>
<br/>
<br/>
</div>
</div>
<div id="healthchecks" class="tab-pane fade">
<!-- tab page "healthchecks" -->
<table id="grid-healthchecks" class="table table-condensed table-hover table-striped table-responsive" data-editDialog="DialogHealthcheck">
<thead>
<tr>
<th data-column-id="healthcheckid" data-type="number" data-visible="false">{{ lang._('Health Check ID') }}</th>
<th data-column-id="name" data-type="string">{{ lang._('Health Check Name') }}</th>
<th data-column-id="description" data-type="string">{{ lang._('Description') }}</th>
<th data-column-id="commands" data-width="7em" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
<th data-column-id="uuid" data-type="string" data-identifier="true" data-visible="false">{{ lang._('ID') }}</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 data-action="deleteSelected" type="button" class="btn btn-xs btn-default"><span class="fa fa-trash-o"></span></button>
</td>
</tr>
</tfoot>
</table>
<!-- apply button -->
<div class="col-md-12">
<hr/>
<button class="btn btn-primary" id="reconfigureAct-healthchecks" type="button"><b>{{ lang._('Apply') }}</b><i id="reconfigureAct_progress" class=""></i></button>
<button class="btn btn-primary" id="configtestAct-healthchecks" type="button"><b>{{ lang._('Test syntax') }}</b><i id="configtestAct_progress" class=""></i></button>
<br/>
<br/>
</div>
</div>
<div id="actions" class="tab-pane fade">
<!-- tab page "actions" -->
<table id="grid-actions" class="table table-condensed table-hover table-striped table-responsive" data-editDialog="DialogAction">
<thead>
<tr>
<th data-column-id="actionid" data-type="number" data-visible="false">{{ lang._('Action ID') }}</th>
<th data-column-id="name" data-type="string">{{ lang._('Action Name') }}</th>
<th data-column-id="description" data-type="string">{{ lang._('Description') }}</th>
<th data-column-id="commands" data-width="7em" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
<th data-column-id="uuid" data-type="string" data-identifier="true" data-visible="false">{{ lang._('ID') }}</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 data-action="deleteSelected" type="button" class="btn btn-xs btn-default"><span class="fa fa-trash-o"></span></button>
</td>
</tr>
</tfoot>
</table>
<!-- apply button -->
<div class="col-md-12">
<hr/>
<button class="btn btn-primary" id="reconfigureAct-actions" type="button"><b>{{ lang._('Apply') }}</b><i id="reconfigureAct_progress" class=""></i></button>
<button class="btn btn-primary" id="configtestAct-actions" type="button"><b>{{ lang._('Test syntax') }}</b><i id="configtestAct_progress" class=""></i></button>
<br/>
<br/>
</div>
</div>
<div id="acls" class="tab-pane fade">
<!-- tab page "acls" -->
<table id="grid-acls" class="table table-condensed table-hover table-striped table-responsive" data-editDialog="DialogAcl">
<thead>
<tr>
<th data-column-id="aclid" data-type="number" data-visible="false">{{ lang._('ACL id') }}</th>
<th data-column-id="name" data-type="string">{{ lang._('ACL Name') }}</th>
<th data-column-id="description" data-type="string">{{ lang._('Description') }}</th>
<th data-column-id="commands" data-width="7em" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
<th data-column-id="uuid" data-type="string" data-identifier="true" data-visible="false">{{ lang._('ID') }}</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 data-action="deleteSelected" type="button" class="btn btn-xs btn-default"><span class="fa fa-trash-o"></span></button>
</td>
</tr>
</tfoot>
</table>
<!-- apply button -->
<div class="col-md-12">
<hr/>
<button class="btn btn-primary" id="reconfigureAct-acls" type="button"><b>{{ lang._('Apply') }}</b><i id="reconfigureAct_progress" class=""></i></button>
<button class="btn btn-primary" id="configtestAct-acls" type="button"><b>{{ lang._('Test syntax') }}</b><i id="configtestAct_progress" class=""></i></button>
<br/>
<br/>
</div>
</div>
<div id="luas" class="tab-pane fade">
<!-- tab page "luas" -->
<table id="grid-luas" class="table table-condensed table-hover table-striped table-responsive" data-editDialog="DialogLua">
<thead>
<tr>
<th data-column-id="enabled" data-width="6em" data-type="string" data-formatter="rowtoggle">{{ lang._('Enabled') }}</th>
<th data-column-id="luaid" data-type="number" data-visible="false">{{ lang._('Lua ID') }}</th>
<th data-column-id="name" data-type="string">{{ lang._('Lua Script Name') }}</th>
<th data-column-id="description" data-type="string">{{ lang._('Description') }}</th>
<th data-column-id="commands" data-width="7em" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
<th data-column-id="uuid" data-type="string" data-identifier="true" data-visible="false">{{ lang._('ID') }}</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 data-action="deleteSelected" type="button" class="btn btn-xs btn-default"><span class="fa fa-trash-o"></span></button>
</td>
</tr>
</tfoot>
</table>
<!-- apply button -->
<div class="col-md-12">
<hr/>
<button class="btn btn-primary" id="reconfigureAct-luas" type="button"><b>{{ lang._('Apply') }}</b><i id="reconfigureAct_progress" class=""></i></button>
<button class="btn btn-primary" id="configtestAct-luas" type="button"><b>{{ lang._('Test syntax') }}</b><i id="configtestAct_progress" class=""></i></button>
<br/>
<br/>
</div>
</div>
<div id="errorfiles" class="tab-pane fade">
<!-- tab page "errorfiles" -->
<table id="grid-errorfiles" class="table table-condensed table-hover table-striped table-responsive" data-editDialog="DialogErrorfile">
<thead>
<tr>
<th data-column-id="errorfileid" data-type="number" data-visible="false">{{ lang._('Error File ID') }}</th>
<th data-column-id="name" data-type="string">{{ lang._('Name') }}</th>
<th data-column-id="description" data-type="string">{{ lang._('Description') }}</th>
<th data-column-id="commands" data-width="7em" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
<th data-column-id="uuid" data-type="string" data-identifier="true" data-visible="false">{{ lang._('ID') }}</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 data-action="deleteSelected" type="button" class="btn btn-xs btn-default"><span class="fa fa-trash-o"></span></button>
</td>
</tr>
</tfoot>
</table>
<!-- apply button -->
<div class="col-md-12">
<hr/>
<button class="btn btn-primary" id="reconfigureAct-errorfiles" type="button"><b>{{ lang._('Apply') }}</b><i id="reconfigureAct_progress" class=""></i></button>
<button class="btn btn-primary" id="configtestAct-errorfiles" type="button"><b>{{ lang._('Test syntax') }}</b><i id="configtestAct_progress" class=""></i></button>
<br/>
<br/>
</div>
</div>
</div>
{# include dialogs #}
{{ partial("layout_partials/base_dialog",['fields':formDialogFrontend,'id':'DialogFrontend','label':'Edit Frontend'])}}
{{ partial("layout_partials/base_dialog",['fields':formDialogBackend,'id':'DialogBackend','label':'Edit Backend'])}}
{{ partial("layout_partials/base_dialog",['fields':formDialogServer,'id':'DialogServer','label':'Edit Server'])}}
{{ partial("layout_partials/base_dialog",['fields':formDialogHealthcheck,'id':'DialogHealthcheck','label':'Edit Health Check'])}}
{{ partial("layout_partials/base_dialog",['fields':formDialogAction,'id':'DialogAction','label':'Edit Action'])}}
{{ partial("layout_partials/base_dialog",['fields':formDialogAcl,'id':'DialogAcl','label':'Edit ACL'])}}
{{ partial("layout_partials/base_dialog",['fields':formDialogLua,'id':'DialogLua','label':'Edit Lua Script'])}}
{{ partial("layout_partials/base_dialog",['fields':formDialogErrorfile,'id':'DialogErrorfile','label':'Edit Error File'])}}

View file

@ -0,0 +1,311 @@
{#
Copyright (C) 2016 Frank Wall
OPNsense® is Copyright © 2014 2016 by Deciso B.V.
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.
#}
<script type="text/javascript">
$( document ).ready(function() {
var gridopt = {
ajax: false,
selection: false,
multiSelect: false
};
$("#grid-status").bootgrid('destroy');
$("#grid-status").bootgrid(gridopt);
// update info
$("#update-info").click(function() {
$('#processing-dialog').modal('show');
$('#updatelist').empty();
ajaxGet(url = "/api/haproxy/statistics/info/", sendData={},
callback = function (data, status) {
if (status == "success") {
$("#infolist").html("<tr><th>{{ lang._('Name') }}</th><th>{{ lang._('Value') }}</th></tr>");
$.each(data, function (key, value) {
$('#infolist').append('<tr><td>'+key+'</td>' +
"<td>"+value+"</td></tr>");
});
}
$('#processing-dialog').modal('hide');
}
);
});
// update status
$("#update-status").click(function() {
$('#processing-dialog').modal('show');
ajaxGet(url = "/api/haproxy/statistics/counters/", sendData={},
callback = function (data, status) {
if (status == "success") {
// status
$("#grid-status").bootgrid('destroy');
var html = [];
$.each(data, function (key, value) {
var fields = ["id", "pxname", "svname", "status", "lastchg", "weight", "act", "downtime"];
tr_str = '<tr>';
for (var i = 0; i < fields.length; i++) {
if (value[fields[i]] != null) {
tr_str += '<td>' + value[fields[i]] + '</td>';
} else {
tr_str += '<td></td>';
}
}
tr_str += '</tr>';
html.push(tr_str);
});
$("#grid-status > tbody").html(html.join(''));
$("#grid-status").bootgrid(gridopt);
}
$('#processing-dialog').modal('hide');
}
);
});
// update counters
$("#update-counters").click(function() {
$('#processing-dialog').modal('show');
ajaxGet(url = "/api/haproxy/statistics/counters/", sendData={},
callback = function (data, status) {
if (status == "success") {
// counters
$("#grid-counters").bootgrid('destroy');
var html = [];
$.each(data, function (key, value) {
var fields = ["id", "pxname", "svname", "qcur", "qmax", "qlimit", "rate", "rate_max", "rate_lim", "scur", "smax", "slim", "stot", "bin", "bout", "dreq", "dresp", "ereq", "econ", "eresp", "wretr", "wredis"];
tr_str = '<tr>';
for (var i = 0; i < fields.length; i++) {
if (value[fields[i]] != null) {
tr_str += '<td>' + value[fields[i]] + '</td>';
} else {
tr_str += '<td></td>';
}
}
tr_str += '</tr>';
html.push(tr_str);
});
$("#grid-counters> tbody").html(html.join(''));
$("#grid-counters").bootgrid(gridopt);
}
$('#processing-dialog').modal('hide');
}
);
});
// update tables
$("#update-tables").click(function() {
$('#processing-dialog').modal('show');
ajaxGet(url = "/api/haproxy/statistics/tables/", sendData={},
callback = function (data, status) {
if (status == "success") {
// tables
$("#grid-tables").bootgrid('destroy');
var html = [];
$.each(data, function (key, value) {
var fields = ["table", "type", "size", "used"];
tr_str = '<tr>';
for (var i = 0; i < fields.length; i++) {
if (value[fields[i]] != null) {
tr_str += '<td>' + value[fields[i]] + '</td>';
} else {
tr_str += '<td></td>';
}
}
tr_str += '</tr>';
html.push(tr_str);
});
$("#grid-tables> tbody").html(html.join(''));
$("#grid-tables").bootgrid(gridopt);
}
$('#processing-dialog').modal('hide');
}
);
});
// initial load
$("#update-info").click();
$("#update-status").click();
$("#update-counters").click();
$("#update-tables").click();
});
</script>
<ul class="nav nav-tabs" role="tablist" id="maintabs">
<li class="active"><a data-toggle="tab" href="#info"><b>{{ lang._('General') }}</b></a></li>
<li><a data-toggle="tab" href="#status"><b>{{ lang._('Status') }}</b></a></li>
<li><a data-toggle="tab" href="#counters"><b>{{ lang._('Counters') }}</b></a></li>
<li><a data-toggle="tab" href="#tables"><b>{{ lang._('Stick Tables') }}</b></a></li>
</ul>
<div class="content-box tab-content">
<div id="info" class="tab-pane fade in active">
<!-- tab page "info" -->
<table id="infolist" class="table table-striped table-condensed table-responsive">
</table>
<div class="col-sm-12">
<div class="row">
<table class="table">
<tr>
<td>
<div class="pull-right">
<button id="update-info" type="button" class="btn btn-default">
<span>{{ lang._('Refresh') }}</span>
<span class="fa fa-refresh"></span>
</button>
</div>
</td>
</tr>
</table>
</div>
<hr/>
</div>
</div>
<div id="status" class="tab-pane fade in">
<!-- tab page "status" -->
<table id="grid-status" class="table table-condensed table-hover table-striped table-responsive">
<thead>
<tr>
<th data-column-id="id" data-type="string" data-identifier="true" data-visible="false">{{ lang._('id') }}</th>
<th data-column-id="pxname" data-type="string">{{ lang._('Proxy') }}</th>
<th data-column-id="svname" data-type="string">{{ lang._('Server') }}</th>
<th data-column-id="status" data-type="string">{{ lang._('Status') }}</th>
<th data-column-id="lastchg" data-type="string">{{ lang._('Last Change') }}</th>
<th data-column-id="weight" data-type="string">{{ lang._('Weight') }}</th>
<th data-column-id="act" data-type="string">{{ lang._('Active') }}</th>
<th data-column-id="downtime" data-type="string">{{ lang._('Downtime') }}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div class="col-sm-12">
<div class="row">
<table class="table">
<tr>
<td>
<div class="pull-right">
<button id="update-status" type="button" class="btn btn-default">
<span>{{ lang._('Refresh') }}</span>
<span class="fa fa-refresh"></span>
</button>
</div>
</td>
</tr>
</table>
</div>
<hr/>
</div>
</div>
<div id="counters" class="tab-pane fade in">
<!-- tab page "counters" -->
<table id="grid-counters" class="table table-condensed table-hover table-striped table-responsive">
<thead>
<tr>
<th data-column-id="id" data-type="string" data-identifier="true" data-visible="false">{{ lang._('id') }}</th>
<th data-column-id="pxname" data-type="string" data-identifier="true">{{ lang._('Backend/Frontend') }}</th>
<th data-column-id="svname" data-type="string">{{ lang._('Server') }}</th>
<th data-column-id="qcur" data-type="string">{{ lang._('Queue') }}</th>
<th data-column-id="qmax" data-type="string">{{ lang._('Max') }}</th>
<th data-column-id="qlimit" data-type="string">{{ lang._('Limit') }}</th>
<th data-column-id="rate" data-type="string">{{ lang._('Session Rate') }}</th>
<th data-column-id="rate_max" data-type="string">{{ lang._('Max') }}</th>
<th data-column-id="rate_lim" data-type="string">{{ lang._('Limit') }}</th>
<th data-column-id="scur" data-type="string">{{ lang._('Sessions') }}</th>
<th data-column-id="smax" data-type="string">{{ lang._('Max') }}</th>
<th data-column-id="slim" data-type="string">{{ lang._('Limit') }}</th>
<th data-column-id="stot" data-type="string">{{ lang._('Total') }}</th>
<th data-column-id="bin" data-type="string">{{ lang._('Bytes In') }}</th>
<th data-column-id="bout" data-type="string">{{ lang._('Out') }}</th>
<th data-column-id="dreq" data-type="string">{{ lang._('Denied Req') }}</th>
<th data-column-id="dresp" data-type="string">{{ lang._('Resp') }}</th>
<th data-column-id="ereq" data-type="string">{{ lang._('Errors Req') }}</th>
<th data-column-id="econ" data-type="string">{{ lang._('Conn') }}</th>
<th data-column-id="eresp" data-type="string">{{ lang._('Resp') }}</th>
<th data-column-id="wretr" data-type="string">{{ lang._('Warnings Retr') }}</th>
<th data-column-id="wredis" data-type="string">{{ lang._('Redis') }}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div class="col-sm-12">
<div class="row">
<table class="table">
<tr>
<td>
<div class="pull-right">
<button id="update-counters" type="button" class="btn btn-default">
<span>{{ lang._('Refresh') }}</span>
<span class="fa fa-refresh"></span>
</button>
</div>
</td>
</tr>
</table>
</div>
<hr/>
</div>
</div>
<div id="tables" class="tab-pane fade in">
<!-- tab page "tables" -->
<table id="grid-tables" class="table table-condensed table-hover table-striped table-responsive">
<thead>
<tr>
<th data-column-id="table" data-type="string" data-identifier="true">{{ lang._('Table') }}</th>
<th data-column-id="type" data-type="string">{{ lang._('Type') }}</th>
<th data-column-id="size" data-type="string">{{ lang._('Size') }}</th>
<th data-column-id="used" data-type="string">{{ lang._('Used') }}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div class="col-sm-12">
<div class="row">
<table class="table">
<tr>
<td>
<div class="pull-right">
<button id="update-tables" type="button" class="btn btn-default">
<span>{{ lang._('Refresh') }}</span>
<span class="fa fa-refresh"></span>
</button>
</div>
</td>
</tr>
</table>
</div>
<hr/>
</div>
</div>
</div>
{{ partial("layout_partials/base_dialog_processing") }}

View file

@ -0,0 +1,66 @@
#!/usr/local/bin/php
<?php
/**
* Copyright (C) 2016 Frank Wall
* Copyright (C) 2015 Deciso B.V.
*
* 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.
*
*/
// Use legacy code to export certificates to the filesystem.
require_once("config.inc");
require_once("certs.inc");
require_once("legacy_bindings.inc");
use OPNsense\Core\Config;
global $config;
// traverse HAProxy frontends
$configObj = Config::getInstance()->object();
if (isset($configObj->OPNsense->HAProxy->frontends)) {
foreach ($configObj->OPNsense->HAProxy->frontends->children() as $frontend) {
if (!isset($frontend->ssl)) {
continue;
}
// multiple comma-separated values are possible
$certs = explode(',', $frontend->ssl->certificates);
foreach ($certs as $cert_refid) {
// if the frontend has a cert attached, search for its contents
if ($cert_refid != "") {
foreach ($configObj->cert as $cert) {
if ($cert_refid == (string)$cert->refid) {
// generate cert pem file
$pem_content = str_replace("\n\n", "\n", str_replace("\r", "", base64_decode((string)$cert->crt)));
$pem_content .= str_replace("\n\n", "\n", str_replace("\r", "", base64_decode((string)$cert->prv)));
$output_pem_filename = "/var/etc/haproxy/ssl/" . $cert_refid . ".pem" ;
file_put_contents($output_pem_filename, $pem_content);
chmod($output_pem_filename, 0600);
echo "certificate exported to " . $output_pem_filename . "\n";
}
}
}
}
}
}

View file

@ -0,0 +1,54 @@
#!/usr/local/bin/php
<?php
/**
* Copyright (C) 2016 Frank Wall
* Copyright (C) 2015 Deciso B.V.
*
* 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.
*
*/
// Use legacy code to export certificates to the filesystem.
require_once("config.inc");
require_once("certs.inc");
require_once("legacy_bindings.inc");
use OPNsense\Core\Config;
global $config;
// traverse HAProxy error files
$configObj = Config::getInstance()->object();
if (isset($configObj->OPNsense->HAProxy->errorfiles)) {
foreach ($configObj->OPNsense->HAProxy->errorfiles->children() as $errorfile) {
$ef_name = (string)$errorfile->name;
$ef_id = (string)$errorfile->id;
if ($ef_id != "") {
$ef_content = str_replace("\n\n", "\n", str_replace("\r", "", (string)$errorfile->content));
$ef_filename = "/var/etc/haproxy/errorfiles/" . $ef_id . ".txt" ;
file_put_contents($ef_filename, $ef_content);
chmod($ef_filename, 0600);
echo "error file exported to " . $ef_filename . "\n";
}
}
}

View file

@ -0,0 +1,57 @@
#!/usr/local/bin/php
<?php
/**
* Copyright (C) 2016 Frank Wall
* Copyright (C) 2015 Deciso B.V.
*
* 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.
*
*/
// Use legacy code to export certificates to the filesystem.
require_once("config.inc");
require_once("certs.inc");
require_once("legacy_bindings.inc");
use OPNsense\Core\Config;
global $config;
// traverse HAProxy Lua scripts
$configObj = Config::getInstance()->object();
if (isset($configObj->OPNsense->HAProxy->luas)) {
foreach ($configObj->OPNsense->HAProxy->luas->children() as $lua) {
if (!isset($lua->enabled)) {
continue;
}
$lua_name = (string)$lua->name;
$lua_id = (string)$lua->id;
if ($lua_id != "") {
$lua_content = str_replace("\n\n", "\n", str_replace("\r", "", (string)$lua->content));
$lua_filename = "/var/etc/haproxy/lua/" . $lua_id . ".lua" ;
file_put_contents($lua_filename, $lua_content);
chmod($lua_filename, 0600);
echo "lua script exported to " . $lua_filename . "\n";
}
}
}

View file

@ -0,0 +1,116 @@
#!/usr/local/bin/php
<?php
/**
* Copyright (C) 2016 Frank Wall
*
* 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.
*
*/
$haproxy_socket = '/var/run/haproxy.socket';
function socketCmd($command) {
global $haproxy_socket;
$data = array();
if (!file_exists($haproxy_socket)) {
throw new UnexpectedValueException("HAProxy socket does not exist, service may be stopped");
} else {
$socket = @stream_socket_client("unix://$haproxy_socket", $errorNumber, $errorMessage);
if (!$socket) {
throw new UnexpectedValueException("Unable to open socket: $errorMessage");
} else {
fwrite($socket, "$command\n");
while (!feof($socket)) {
// XXX: The #-prefix is annoying.
$data[] = trim(preg_replace('/#/', '', fgets($socket)));
}
fclose($socket);
}
}
return $data;
}
function showInfo() {
$data = array();
$show_info = socketCmd('show info');
foreach ($show_info as $line ) {
if (empty(trim($line))) continue;
$values = explode(':', $line);
$data[trim($values[0])] = trim($values[1]);
}
return $data;
}
function showTable() {
$data = array();
$show_table = socketCmd('show table');
foreach ($show_table as $line) {
if (empty(trim($line))) continue;
$line = preg_replace('/#/', '', $line);
$values = explode(',', $line);
$items = array();
foreach ($values as $value) {
$item = explode(':', trim($value));
$items[$item[0]] = trim($item[1]);
}
$data[] = $items;
}
return $data;
}
function showStat() {
$show_stat = socketCmd('show stat');
// output is a list of CSV
$stat_csv = array_map('str_getcsv', $show_stat);
array_walk($stat_csv, function(&$a) use ($stat_csv) {
// XXX: Ignore empty/incomplete entries.
if (count($a) > 1) $a = array_combine($stat_csv[0], $a);
});
array_shift($stat_csv); # remove column header
foreach ($stat_csv as &$value) {
// Add unique identifier.
if ( ! empty($value['pxname']) and $value['pxname'] != null ) {
$value['id'] = $value['pxname'] . '/' . $value['svname'];
}
}
return $stat_csv;
}
switch ($argv[1]) {
case 'info':
$result = showInfo();
echo json_encode($result);
break;
case 'table':
$result = showTable();
echo json_encode($result);
break;
case 'stat':
$result = showStat();
echo json_encode($result);
break;
default:
echo "not a valid argument\n";
}

View file

@ -0,0 +1,19 @@
#!/bin/sh
HAPROXY_DIRS="/var/log/haproxy /var/run/haproxy /var/etc/haproxy/ssl /var/etc/haproxy/lua /var/etc/haproxy/errorfiles"
for directory in ${HAPROXY_DIRS}; do
mkdir -p ${directory}
chown -R www:www ${directory}
chmod -R 750 ${directory}
done
# chroot dir must not be writable
chmod 550 /var/run/haproxy
# export required data to filesystem
/usr/local/opnsense/scripts/OPNsense/HAProxy/exportCerts.php > /dev/null 2>&1
/usr/local/opnsense/scripts/OPNsense/HAProxy/exportLuaScripts.php > /dev/null 2>&1
/usr/local/opnsense/scripts/OPNsense/HAProxy/exportErrorFiles.php > /dev/null 2>&1
exit 0

View file

@ -0,0 +1,47 @@
[setup]
command:/usr/local/opnsense/scripts/OPNsense/HAProxy/setup.sh
parameters:
type:script_output
message:setup haproxy service requirements
[start]
command:/usr/local/opnsense/scripts/OPNsense/HAProxy/setup.sh; /usr/local/etc/rc.d/haproxy start
parameters:
type:script
message:starting haproxy
[stop]
command:/usr/local/etc/rc.d/haproxy stop; /usr/bin/killall haproxy; exit 0
parameters:
type:script
message:stopping haproxy
[restart]
command:/usr/local/opnsense/scripts/OPNsense/HAProxy/setup.sh; /usr/local/etc/rc.d/haproxy restart
parameters:
type:script
message:restarting haproxy
[reconfigure]
command:/usr/local/opnsense/scripts/OPNsense/HAProxy/setup.sh; /usr/local/etc/rc.d/haproxy reload
parameters:
type:script
message:reconfiguring haproxy
[configtest]
command:/usr/local/etc/rc.d/haproxy configtest 2>&1 || exit 0
parameters:
type:script_output
message:testing haproxy configuration
[status]
command:/usr/local/etc/rc.d/haproxy status || exit 0
parameters:
type:script_output
message:requesting haproxy status
[statistics]
command:/usr/local/opnsense/scripts/OPNsense/HAProxy/queryStats.php
parameters:%s
type:script_output
message:requesting haproxy statistics

View file

@ -0,0 +1,8 @@
name: opnsense-haproxy
version: 1.0
origin: opnsense/haproxy
comment: load balancer
desc: Reliable, high performance TCP/HTTP load balancer
maintainer: opnsense at moov.de
www: https://haproxy.org
prefix: /

View file

@ -0,0 +1,2 @@
haproxy.conf:/usr/local/etc/haproxy.conf
rc.conf.d:/etc/rc.conf.d/haproxy

View file

@ -0,0 +1,790 @@
#
# Automatically generated configuration.
# Do not edit this file manually.
{# ############################### #}
{# MACROS #}
{# ############################### #}
{# Macro expects a CSV list of Error Files and validates them. #}
{% macro ErrorFiles(linkedData) -%}
{% if linkedData is defined %}
{# # remember all Errorfiles to avoid duplicate HTTP codes #}
{% set http_codes_seen = [] %}
{% for errorfile in linkedData.split(",") %}
{% set errorfile_data = helpers.getUUID(errorfile) %}
{% if errorfile_data.code in http_codes_seen %}
# ERROR FILE INVALID: {{errorfile_data.name}}
# ERROR: this HTTP code is already used by another error file in this context
{% else %}
{% do http_codes_seen.append(errorfile_data.code) %}
# ERROR FILE: {{errorfile_data.name}}
errorfile {{errorfile_data.code|replace("x", "")}} /var/etc/haproxy/errorfiles/{{errorfile_data.id}}.txt
{% endif %}
{% endfor %}
{% else %}
# ERROR: ErrorFiles called with empty data
{% endif %}
{%- endmacro %}
{# Macro expects a CSV list of Actions and validates them. #}
{% macro AclsAndActions(linkedData) -%}
{% if linkedData is defined %}
{# # remember all ACLs to avoid duplicate declarations #}
{% set acls_seen = [] %}
{% for action in linkedData.split(",") %}
{% set action_data = helpers.getUUID(action) %}
{# # collect ACLs for this action #}
{% set action_acls = [] %}
{# # collect ACL errors (may disable Action) #}
{% set acl_errors = '0' %}
{# # An action with no ACLs is invalid #}
{% if action_data.linkedAcls|default("") != "" %}
{% for acl in action_data.linkedAcls.split(",") %}
{% set acl_data = helpers.getUUID(acl) %}
{% set acl_enabled = '1' %}
{% do action_acls.append('acl_' ~ acl_data.id) %}
{# # check if this ACL was already defined in this scope #}
{% if acl_data.id in acls_seen %}
{# # DEBUG: ignoring duplicate ACL {{acl_data.name}} #}
{% continue %}
{% endif %}
{% do acls_seen.append(acl_data.id) %}
{% set acl_options = [] %}
{% if acl_data.expression == 'host_starts_with' %}
{% if acl_data.value|default("") != "" %}
{% do acl_options.append('hdr_beg(host) -i ' ~ acl_data.value) %}
{% else %}
{% set acl_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif acl_data.expression == 'host_ends_with' %}
{% if acl_data.value|default("") != "" %}
{% do acl_options.append('hdr_end(host) -i ' ~ acl_data.value) %}
{% else %}
{% set acl_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif acl_data.expression == 'host_matches' %}
{% if acl_data.value|default("") != "" %}
{% do acl_options.append('hdr(host) -i ' ~ acl_data.value) %}
{% else %}
{% set acl_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif acl_data.expression == 'host_regex' %}
{% if acl_data.value|default("") != "" %}
{% do acl_options.append('hdr_reg(host) -i ' ~ acl_data.value) %}
{% else %}
{% set acl_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif acl_data.expression == 'host_contains' %}
{% if acl_data.value|default("") != "" %}
{% do acl_options.append('hdr_dir(host) -i ' ~ acl_data.value) %}
{% else %}
{% set acl_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif acl_data.expression == 'path_starts_with' %}
{% if acl_data.value|default("") != "" %}
{% do acl_options.append('path_beg -i ' ~ acl_data.value) %}
{% else %}
{% set acl_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif acl_data.expression == 'path_ends_with' %}
{% if acl_data.value|default("") != "" %}
{% do acl_options.append('path_end -i ' ~ acl_data.value) %}
{% else %}
{% set acl_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif acl_data.expression == 'path_matches' %}
{% if acl_data.value|default("") != "" %}
{% do acl_options.append('path -i ' ~ acl_data.value) %}
{% else %}
{% set acl_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif acl_data.expression == 'path_regex' %}
{% if acl_data.value|default("") != "" %}
{% do acl_options.append('path_reg -i ' ~ acl_data.value) %}
{% else %}
{% set acl_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif acl_data.expression == 'path_contains' %}
{% if acl_data.value|default("") != "" %}
{% do acl_options.append('path_dir -i ' ~ acl_data.value) %}
{% else %}
{% set acl_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif acl_data.expression == 'url_parameter' %}
{% if acl_data.value|default("") != "" and acl_data.urlparam|default("") != "" %}
{% do acl_options.append('url_param(' ~ acl_data.urlparam ~ ') -i ' ~ acl_data.value) %}
{% else %}
{% set acl_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif acl_data.expression == 'ssl_c_verify_code' %}
{% if acl_data.value|default("") != "" %}
{% do acl_options.append('ssl_c_verify ' ~ acl_data.value) %}
{% else %}
{% set acl_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif acl_data.expression == 'ssl_c_verify' %}
{% do acl_options.append('ssl_c_verify 0') %}
{% elif acl_data.expression == 'ssl_c_ca_commonname' %}
{% if acl_data.value|default("") != "" %}
{% do acl_options.append('ssl_c_i_dn(CN) ' ~ acl_data.value) %}
{% else %}
{% set acl_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif acl_data.expression == 'source_ip' %}
{% if acl_data.value|default("") != "" %}
{% do acl_options.append('src ' ~ acl_data.value) %}
{% else %}
{% set acl_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif acl_data.expression == 'backendservercount' %}
{% do acl_options.append('') %}
{% if acl_data.value|default("") != "" and acl_data.queryBackend|default("") != "" %}
{% do acl_options.append('nbsrv(backend_' ~ acl_data.queryBackend ~ ') ge ' ~ acl_data.value) %}
{% else %}
{% set acl_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif acl_data.expression == 'traffic_is_http' %}
{% do acl_options.append('req.proto_http') %}
{% elif acl_data.expression == 'traffic_is_ssl' %}
{% do acl_options.append('req.ssl_ver gt 0') %}
{% elif acl_data.expression == 'ssl_sni_matches' %}
{% if acl_data.value|default("") != "" %}
{% do acl_options.append('req.ssl_sni -i ' ~ acl_data.value) %}
{% else %}
{% set acl_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif acl_data.expression == 'ssl_sni_contains' %}
{% if acl_data.value|default("") != "" %}
{% do acl_options.append('req.ssl_sni -m sub -i ' ~ acl_data.value) %}
{% else %}
{% set acl_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif acl_data.expression == 'ssl_sni_starts_with' %}
{% if acl_data.value|default("") != "" %}
{% do acl_options.append('req.ssl_sni -m beg -i ' ~ acl_data.value) %}
{% else %}
{% set acl_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif acl_data.expression == 'ssl_sni_ends_with' %}
{% if acl_data.value|default("") != "" %}
{% do acl_options.append('req.ssl_sni -m end -i ' ~ acl_data.value) %}
{% else %}
{% set acl_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif acl_data.expression == 'ssl_sni_regex' %}
{% if acl_data.value|default("") != "" %}
{% do acl_options.append('req.ssl_sni -m reg -i ' ~ acl_data.value) %}
{% else %}
{% set acl_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif acl_data.expression == 'custom_acl' %}
{% if acl_data.value|default("") != "" %}
{% do acl_options.append(acl_data.value) %}
{% else %}
{% set acl_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% else %}
{% set acl_enabled = '0' %}
# ERROR: unsupported ACL expression
{% endif %}
{# # check if ACL is valid #}
{% if acl_enabled == '1' %}
# ACL: {{acl_data.name}}
acl acl_{{acl_data.id}} {{acl_options|join(' ')}}
{% else %}
{% set acl_errors = acl_errors + 1 %}
# ACL INVALID: {{acl_data.name}}
{% endif %}
{% endfor %}
{# # NOTE: We're ignoring actions if any ACL is erroneous, #}
{# # because doing otherwise would lead to unpredictable behaviour. #}
{% if acl_errors == '0' %}
{% set action_enabled = '1' %}
{% set action_options = [] %}
{% if action_data.type == 'use_backend' %}
{% if action_data.useBackend|default("") != "" %}
{% set acl_backend_data = helpers.getUUID(action_data.useBackend) %}
{% do action_options.append('use_backend ' ~ acl_backend_data.name) %}
{% else %}
{% set action_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif action_data.type == 'use_server' %}
{% if action_data.useServer|default("") != "" %}
{% set server_data = helpers.getUUID(action_data.useServer) %}
{% do action_options.append('use-server ' ~ server_data.name) %}
{% else %}
{% set action_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif action_data.type == 'http-request_allow' %}
{% do action_options.append('http-request allow') %}
{% elif action_data.type == 'http-request_deny' %}
{% do action_options.append('http-request deny') %}
{% elif action_data.type == 'http-request_tarpit' %}
{% do action_options.append('http-request tarpit') %}
{% elif action_data.type == 'http-request_auth' %}
{% if action_data.actionValue|default("") != "" %}
{% do action_options.append('http-request auth ' ~ action_data.actionValue) %}
{% else %}
{% set action_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif action_data.type == 'http-request_redirect' %}
{% if action_data.actionValue|default("") != "" %}
{% do action_options.append('http-request redirect ' ~ action_data.actionValue) %}
{% else %}
{% set action_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif action_data.type == 'http-request_lua' %}
{% if action_data.actionValue|default("") != "" %}
{% do action_options.append('http-request lua.' ~ action_data.actionValue) %}
{% else %}
{% set action_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif action_data.type == 'http-request_use-service' %}
{% if action_data.actionValue|default("") != "" %}
{% do action_options.append('http-request use-service lua.' ~ action_data.actionValue) %}
{% else %}
{% set action_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif action_data.type == 'http-request_add-header' %}
{% if action_data.actionValue|default("") != "" and action_data.actionName|default("") != "" %}
{% do action_options.append('http-request add-header ' ~ action_data.actionName ~ ' ' ~ action_data.actionValue) %}
{% else %}
{% set action_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif action_data.type == 'http-request_set-header' %}
{% if action_data.actionValue|default("") != "" and action_data.actionName|default("") != "" %}
{% do action_options.append('http-request set-header ' ~ action_data.actionName ~ ' ' ~ action_data.actionValue) %}
{% else %}
{% set action_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif action_data.type == 'http-request_del-header' %}
{% if action_data.actionName|default("") != "" %}
{% do action_options.append('http-request del-header' ~ action_data.actionName) %}
{% else %}
{% set action_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif action_data.type == 'http-request_replace-header' %}
{% if action_data.actionValue|default("") != "" and action_data.actionName|default("") != "" and action_data.actionFind|default("") != "" %}
{% do action_options.append('http-request replace-header ' ~ action_data.actionName ~ ' ' ~ action_data.actionFind ~ ' ' ~ action_data.actionValue) %}
{% else %}
{% set action_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif action_data.type == 'http-request_replace-value' %}
{% if action_data.actionValue|default("") != "" and action_data.actionName|default("") != "" and action_data.actionFind|default("") != "" %}
{% do action_options.append('http-request replace-value ' ~ action_data.actionName ~ ' ' ~ action_data.actionFind ~ ' ' ~ action_data.actionValue) %}
{% else %}
{% set action_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif action_data.type == 'http-response_allow' %}
{% do action_options.append('http-response allow') %}
{% elif action_data.type == 'http-response_deny' %}
{% do action_options.append('http-response deny') %}
{% elif action_data.type == 'http-response_lua' %}
{% if action_data.actionValue|default("") != "" %}
{% do action_options.append('http-response lua.' ~ action_data.actionValue) %}
{% else %}
{% set action_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif action_data.type == 'http-response_add-header' %}
{% if action_data.actionValue|default("") != "" and action_data.actionName|default("") != "" %}
{% do action_options.append('http-response add-header ' ~ action_data.actionName ~ ' ' ~ action_data.actionValue) %}
{% else %}
{% set action_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif action_data.type == 'http-response_set-header' %}
{% if action_data.actionValue|default("") != "" and action_data.actionName|default("") != "" %}
{% do action_options.append('http-response set-header ' ~ action_data.actionName ~ ' ' ~ action_data.actionValue) %}
{% else %}
{% set action_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif action_data.type == 'http-response_del-header' %}
{% if action_data.actionName|default("") != "" %}
{% do action_options.append('http-response del-header' ~ action_data.actionName) %}
{% else %}
{% set action_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif action_data.type == 'http-response_replace-header' %}
{% if action_data.actionValue|default("") != "" and action_data.actionName|default("") != "" and action_data.actionFind|default("") != "" %}
{% do action_options.append('http-response replace-header ' ~ action_data.actionName ~ ' ' ~ action_data.actionFind ~ ' ' ~ action_data.actionValue) %}
{% else %}
{% set action_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif action_data.type == 'http-response_replace-value' %}
{% if action_data.actionValue|default("") != "" and action_data.actionName|default("") != "" and action_data.actionFind|default("") != "" %}
{% do action_options.append('http-response replace-value ' ~ action_data.actionName ~ ' ' ~ action_data.actionFind ~ ' ' ~ action_data.actionValue) %}
{% else %}
{% set action_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif action_data.type == 'tcp-request_connection_accept' %}
{% do action_options.append('tcp-request connection accept') %}
{% elif action_data.type == 'tcp-request_connection_reject' %}
{% do action_options.append('tcp-request connection reject') %}
{% elif action_data.type == 'tcp-request_content_accept' %}
{% do action_options.append('tcp-request content accept') %}
{% elif action_data.type == 'tcp-request_content_reject' %}
{% do action_options.append('tcp-request content reject') %}
{% elif action_data.type == 'tcp-request_content_lua' %}
{% if action_data.actionValue|default("") != "" %}
{% do action_options.append('tcp-request content lua.' ~ action_data.actionValue) %}
{% else %}
{% set action_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif action_data.type == 'tcp-request_content_use-service' %}
{% if action_data.actionValue|default("") != "" %}
{% do action_options.append('tcp-request content use-service lua.' ~ action_data.actionValue) %}
{% else %}
{% set action_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif action_data.type == 'tcp-response_content_accept' %}
{% do action_options.append('tcp-response content accept') %}
{% elif action_data.type == 'tcp-response_content_close' %}
{% do action_options.append('tcp-response content close') %}
{% elif action_data.type == 'tcp-response_content_reject' %}
{% do action_options.append('tcp-response content reject') %}
{% elif action_data.type == 'tcp-response_content_lua' %}
{% if action_data.actionValue|default("") != "" %}
{% do action_options.append('tcp-response content lua.' ~ action_data.actionValue) %}
{% else %}
{% set action_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% elif action_data.type == 'custom' %}
{% if action_data.actionValue|default("") != "" %}
{% do action_options.append(action_data.actionValue) %}
{% else %}
{% set action_enabled = '0' %}
# ERROR: missing parameters
{% endif %}
{% else %}
{% set action_enabled = '0' %}
# ERROR: unsupported action type
{% endif %}
{# # check if action is valid #}
{% if action_enabled == '1' %}
{% if action_data.operator == 'or' %}
{% set join_operator = ' || ' %}
{% else %}
{% set join_operator = ' ' %}
{% endif %}
# ACTION: {{action_data.name}}
{{action_options|join(' ')}} {{action_data.testType}} {{action_acls|join(join_operator)}}
{% else %}
# ACTION INVALID: {{action_data.name}}
{% endif %}
{% else %}
# ACTION INVALID: {{action_data.name}}
# ACL ERROR COUNT: {{acl_errors}}
{% endif %}
{% else %}
# ERROR: got action with empty linkedAcls
{% endif %}
{% endfor %}
{% else %}
# ERROR: AclsAndActions called with empty data
{% endif %}
{%- endmacro %}
{% if not (helpers.exists('OPNsense.HAProxy.general') and OPNsense.HAProxy.general.enabled|default("0") == "1") %}
#
# NOTE: HAProxy is currently DISABLED
#
{% endif %}
{# ############################### #}
{# GLOBAL #}
{# ############################### #}
global
uid 80
gid 80
chroot /var/run/haproxy
daemon
stats socket /var/run/haproxy.socket level admin
nbproc {{OPNsense.HAProxy.general.tuning.nbproc}}
{% if helpers.exists('OPNsense.HAProxy.general.tuning.maxConnections') %}
maxconn {{OPNsense.HAProxy.general.tuning.maxConnections}}
{% endif %}
{% if helpers.exists('OPNsense.HAProxy.general.tuning.maxDHSize') %}
tune.ssl.default-dh-param {{OPNsense.HAProxy.general.tuning.maxDHSize}}
{% endif %}
{% if OPNsense.HAProxy.general.tuning.spreadChecks|default("") != "" %}
spread-checks {{OPNsense.HAProxy.general.tuning.spreadChecks}}
{% endif %}
{% if OPNsense.HAProxy.general.tuning.checkBufferSize|default("") != "" %}
tune.chksize {{OPNsense.HAProxy.general.tuning.checkBufferSize}}
{% endif %}
{% if OPNsense.HAProxy.general.tuning.bufferSize|default("") != "" %}
tune.bufsize {{OPNsense.HAProxy.general.tuning.bufferSize}}
{% endif %}
{% if OPNsense.HAProxy.general.tuning.luaMaxMem|default("") != "" %}
tune.lua.maxmem {{OPNsense.HAProxy.general.tuning.luaMaxMem}}
{% endif %}
{# # logging configuration #}
{% set logging = [] %}
{% do logging.append(OPNsense.HAProxy.general.logging.host) %}
{% do logging.append('len ' ~ OPNsense.HAProxy.general.logging.length) if OPNsense.HAProxy.general.logging.length|default("") != "" %}
{% do logging.append(OPNsense.HAProxy.general.logging.facility) %}
{% do logging.append(OPNsense.HAProxy.general.logging.level) if OPNsense.HAProxy.general.logging.level|default("") != "" %}
log {{logging|join(' ')}}
{% if OPNsense.HAProxy.luas.lua is defined %}
# lua scripts
{% for lua in helpers.toList('OPNsense.HAProxy.luas.lua') %}
{% if lua.enabled == '1' %}
# lua script: {{lua.name}}
lua-load /var/etc/haproxy/lua/{{lua.id}}.lua
{% endif %}
{% endfor %}
{% endif %}
{# ############################### #}
{# DEFAULTS #}
{# ############################### #}
defaults
log global
{% if OPNsense.HAProxy.general.defaults.redispatch|default("") != "" %}
option redispatch {{OPNsense.HAProxy.general.defaults.redispatch|replace("x", "")}}
{% endif %}
{% if OPNsense.HAProxy.general.defaults.maxConnections|default("") != "" %}
maxconn {{OPNsense.HAProxy.general.defaults.maxConnections}}
{% endif %}
{% if OPNsense.HAProxy.general.defaults.timeoutClient|default("") != "" %}
timeout client {{OPNsense.HAProxy.general.defaults.timeoutClient}}
{% endif %}
{% if OPNsense.HAProxy.general.defaults.timeoutConnect|default("") != "" %}
timeout connect {{OPNsense.HAProxy.general.defaults.timeoutConnect}}
{% endif %}
{% if OPNsense.HAProxy.general.defaults.timeoutServer|default("") != "" %}
timeout server {{OPNsense.HAProxy.general.defaults.timeoutServer}}
{% endif %}
{% if OPNsense.HAProxy.general.defaults.retries|default("") != "" %}
retries {{OPNsense.HAProxy.general.defaults.retries}}
{% endif %}
{# ############################### #}
{# FRONTENDS #}
{# ############################### #}
{% if helpers.exists('OPNsense.HAProxy.frontends') %}
{% for frontend in helpers.toList('OPNsense.HAProxy.frontends.frontend') %}
{% if frontend.enabled=='1' %}
# Frontend: {{frontend.name}} ({{frontend.description}})
frontend {{frontend.name}}
{# # collect ssl certs (if configured) #}
{% if frontend.ssl_certificates|default("") != "" %}
{% set ssl_certs = [] %}
{% for cert in frontend.ssl_certificates.split(",") %}
{% do ssl_certs.append('crt /var/etc/haproxy/ssl/' ~ cert ~ '.pem') %}
{% endfor %}
{% endif %}
{# # advanced ssl options #}
{% if frontend.ssl_customOptions|default("") != "" %}
{# # add a space to separate it from other ssl params #}
{% set ssl_options = frontend.ssl_customOptions ~ ' ' %}
{% endif %}
{# # bind/listen configuration #}
{% if frontend.bind|default("") != "" %}
{% for bind in frontend.bind.split(",") %}
bind {{bind}} name {{bind}} {% if ssl_certs|default("") != "" %}ssl {{ ssl_options }}{{ssl_certs|join(' ')}}{% endif %}
{% endfor %}
{% endif %}
mode {{frontend.mode}}
option {{frontend.connectionBehaviour}}
{# # select backend #}
{% if frontend.defaultBackend|default("") != "" %}
{% set backend_data = helpers.getUUID(frontend.defaultBackend) %}
default_backend {{backend_data.name}}
{% endif %}
# tuning options
{% if frontend.tuning_maxConnections is defined %}
maxconn {{frontend.tuning_maxConnections}}
{% endif %}
{% if frontend.tuning_timeoutClient is defined %}
timeout client {{frontend.tuning_timeoutClient}}
{% elif OPNsense.HAProxy.general.defaults.timeoutClient is defined %}
timeout client {{OPNsense.HAProxy.general.defaults.timeoutClient}}
{% endif %}
# logging options
{% if frontend.logging_dontLogNull=='1' %}
option dontlognull
{% endif %}
{% if frontend.logging_dontLogNormal=='1' %}
option dontlog-normal
{% endif %}
{% if frontend.logging_logSeparateErrors=='1' %}
option log-separate-errors
{% endif %}
{% if frontend.logging_detailedLog=='1' %}
{# # automatically select the best-suited log type #}
{% if frontend.mode == 'tcp' %}
option tcplog
{% else %}
option httplog
{% endif %}
{% endif %}
{% if frontend.logging_socketStats=='1' %}
option socket-stats
{% endif %}
{# # action and ACL configuration #}
{% if frontend.linkedActions|default("") != "" -%}
{# # call macro to evaluate ACLs and actions #}
{{ AclsAndActions(frontend.linkedActions) }}
{%- endif %}
{# # error files #}
{% if frontend.linkedErrorfiles|default("") != "" %}
{# # call macro to evaluate error files #}
{{ ErrorFiles(frontend.linkedErrorfiles) }}
{% endif %}
{% if frontend.customOptions|default("") != "" %}
# WARNING: pass through options below this line
{% for customOpt in frontend.customOptions.split("\n") %}
{{customOpt}}
{% endfor %}
{% endif %}
{% else %}
# Frontend (DISABLED): {{frontend.description}}
{% endif %}
{% endfor %}
{% endif %}
{# ############################### #}
{# BACKENDS #}
{# ############################### #}
{% if helpers.exists('OPNsense.HAProxy.backends') %}
{% for backend in helpers.toList('OPNsense.HAProxy.backends.backend') %}
{# # ignore disabled backends and those without a server #}
{% if backend.enabled == '1' and backend.linkedServers|default("") != "" %}
# Backend: {{backend.name}} ({{backend.description}})
backend {{backend.name}}
{# # store additional parameters for the "server" entries #}
{% set healthcheck_additions = [] %}
{% if backend.healthCheck|default("") != "" and backend.healthCheckEnabled == '1' %}
{% set healthcheck_enabled = '1' %}
{% if backend.healthCheckLogStatus == '1' %}
option log-health-checks
{% endif %}
{% set healthcheck_data = helpers.getUUID(backend.healthCheck) %}
# health check: {{healthcheck_data.name}}
{# # health check option #}
{% set healthcheck_options = [] %}
{% if healthcheck_data.type|default("") == "" %}
{% set healthcheck_enabled = '1' %}
{% elif healthcheck_data.type == 'tcp' %}
{# # TCP check does not require additional options #}
{% elif healthcheck_data.type == 'http' %}
{% do healthcheck_options.append('httpchk') %}
{# # HTTP method must be uppercase #}
{% do healthcheck_options.append(healthcheck_data.http_method|upper) %}
{% do healthcheck_options.append(healthcheck_data.http_uri) %}
{% do healthcheck_options.append('HTTP/1.0') if healthcheck_data.http_version == 'http10' %}
{# # HTTP Host header requires HTTP 1.1 #}
{% do healthcheck_options.append('HTTP/1.1') if healthcheck_data.http_version == 'http11' and healthcheck_data.http_host|default("") == "" %}
{% do healthcheck_options.append('HTTP/1.1\\r\\nHost:\ ' ~ healthcheck_data.http_host) if healthcheck_data.http_version == 'http11' and healthcheck_data.http_host|default("") != "" %}
option {{healthcheck_options|join(' ')}}
{# # custom HTTP health check option #}
{% if healthcheck_data.http_expressionEnabled|default("") == '1' %}
{# # validate options #}
{% if healthcheck_data.http_expression|default("") == "" or healthcheck_data.http_value|default("") == "" %}
# ERROR: invalid custom HTTP health check, missing "expression" or "value"
{% else %}
{% set healthcheck_customhttp = [] %}
{% do healthcheck_customhttp.append(healthcheck_data.http_expression) %}
{% do healthcheck_customhttp.append('!') if healthcheck_data.http_negate == '1' %}
{# # XXX: some values must be properly escaped (whitespace) #}
{% do healthcheck_customhttp.append(healthcheck_data.http_value) %}
http-check expect {{healthcheck_customhttp|join(' ')}}
{% endif %}
{% endif %}
{% elif healthcheck_data.type == 'agent' %}
{% if healthcheck_data.agentPort|default("") != "" %}
{% do healthcheck_additions.append('agent-check agent-port ' ~ healthcheck_data.agentPort) %}
{% else %}
# ERROR: agent-check configured, but agent-port was not specified
{% endif %}
{% elif healthcheck_data.type == 'ldap' %}
option ldap-check
{% elif healthcheck_data.type == 'mysql' or healthcheck_data.type == 'pgsql' %}
{% if healthcheck_data.dbUser|default("") != "" %}
option {{healthcheck_data.type}}-check user {{healthcheck_data.dbUser}}
{% else %}
# ERROR: {{healthcheck_data.type}} check configured, but db user was not specified
{% endif %}
{% elif healthcheck_data.type == 'redis' %}
option redis-check
{% elif healthcheck_data.type == 'smtp' %}
option smtpchk HELO {{healthcheck_data.smtpDomain}}
{% elif healthcheck_data.type == 'esmtp' %}
option smtpchk EHLO {{healthcheck_data.smtpDomain}}
{% elif healthcheck_data.type == 'ssl' %}
option ssl-hello-chk
{% endif %}
{% else %}
# health checking is DISABLED
{% set healthcheck_enabled = '0' %}
{% endif %}
{% for server in backend.linkedServers.split(",") %}
{% set server_data = helpers.getUUID(server) %}
{# # collect optional server parameters #}
{% set server_options = [] %}
{# # check if health check is enabled #}
{% if healthcheck_enabled == '1' %}
{% do server_options.append('check') %}
{% do server_options.append('inter ' ~ server_data.checkInterval) %}
{# # add all additions from healthchecks here #}
{% do server_options.append(healthcheck_additions|join(' ')) if healthcheck_additions.length != '0' %}
{% endif %}
{# # server weight #}
{% do server_options.append('weight ' ~ server_data.weight) if server_data.weight|default("") != "" %}
{# # server role/mode #}
{% do server_options.append(server_data.mode) if server_data.mode|default("") != "active" %}
server {{server_data.name}} {{server_data.address}}:{{server_data.port}} {{server_options|join(' ')}}
{% endfor %}
{# # XXX: Usually the frontend and the backend are in the same mode, #}
{# # but we have no way to know what frontend uses this backend. #}
{# # Hence we can't automatically set the mode and thus need a #}
{# # (redundant) GUI option for this. #}
mode {{backend.mode}}
balance {{backend.algorithm}}
{# # ignore if stickiness is disabled (set to "None") #}
{% if backend.stickiness_pattern|default("") != "" %}
# stickiness
{% if backend.stickiness_pattern == "sourceipv4" %}
stick-table type ip size {{backend.stickiness_size}} expire {{backend.stickiness_expire}}
stick on src
{% elif backend.stickiness_pattern == "sourceipv6" %}
stick-table type ipv6 size {{backend.stickiness_size}} expire {{backend.stickiness_expire}}
stick on src
{% elif backend.stickiness_pattern == "cookievalue" %}
stick-table type string len {{backend.stickiness_cookielength}} size {{backend.stickiness_size}} expire {{backend.stickiness_expire}}
stick store-response res.cook({{backend.stickiness_cookiename}})
stick on req.cook({{backend.stickiness_cookiename}})
{% elif backend.stickiness_pattern == "rdpcookie" %}
stick-table type binary len {{backend.stickiness_cookielength}} size {{backend.stickiness_size}} expire {{backend.stickiness_expire}}
stick on req.rdp_cookie(mstshash)
{% endif %}
{% endif %}
# tuning options
{% if backend.tuning_timeoutConnect|default("") != "" %}
timeout connect {{backend.tuning_timeoutConnect}}
{% elif OPNsense.HAProxy.general.defaults.timeoutConnect is defined %}
timeout connect {{OPNsense.HAProxy.general.defaults.timeoutConnect}}
{% endif %}
{% if backend.tuning_timeoutServer|default("") != "" %}
timeout server {{backend.tuning_timeoutServer}}
{% elif OPNsense.HAProxy.general.defaults.timeoutServer is defined %}
timeout server {{OPNsense.HAProxy.general.defaults.timeoutServer}}
{% endif %}
{% if backend.tuning_retries|default("") != "" %}
retries {{backend.tuning_retries}}
{% endif %}
{# # action and ACL configuration #}
{% if backend.linkedActions|default("") != "" -%}
{# # call macro to evaluate ACLs and actions #}
{{ AclsAndActions(backend.linkedActions) }}
{%- endif %}
{# # error files #}
{% if backend.linkedErrorfiles|default("") != "" %}
{# # call macro to evaluate error files #}
{{ ErrorFiles(backend.linkedErrorfiles) }}
{% endif %}
{% if backend.customOptions|default("") != "" %}
# WARNING: pass through options below this line
{% for customOpt in backend.customOptions.split("\n") %}
{{customOpt}}
{% endfor %}
{% endif %}
{% else %}
# Backend (DISABLED): {{backend.description}}
{% endif %}
{% endfor %}
{% endif %}
{# ############################### #}
{# STATISTICS #}
{# ############################### #}
{% if OPNsense.HAProxy.general.stats.enabled|default("") == "1" %}
{# # enable local stats #}
listen local_statistics
bind 127.0.0.1:{{OPNsense.HAProxy.general.stats.port}}
mode http
stats uri /haproxy?stats
stats realm HAProxy\ statistics
stats admin if TRUE
{# # remote stats are optional #}
{% if OPNsense.HAProxy.general.stats.remoteEnabled|default("") == "1" %}
{% if OPNsense.HAProxy.general.stats.remoteBind|default("") != "" %}
listen remote_statistics
{% for bind in OPNsense.HAProxy.general.stats.remoteBind.split(",") %}
bind {{bind}}
{% endfor %}
mode http
stats uri /haproxy?stats
stats realm HAProxy\ statistics
stats hide-version
{# # enable authentication? #}
{% if OPNsense.HAProxy.general.stats.authEnabled|default("") == "1" %}
{% if OPNsense.HAProxy.general.stats.users|default("") != "" %}
{% for statsuser in OPNsense.HAProxy.general.stats.users.split(",") %}
stats auth {{statsuser}}
{% endfor %}
{% endif %}
{% endif %}
{% else %}
# ERROR: remote statistics disabled, because no listen address was specified
{% endif %}
{% endif %}
{% else %}
# statistics are DISABLED
{% endif %}

View file

@ -0,0 +1,4 @@
haproxy_enable="{% if helpers.exists('OPNsense.HAProxy.general.enabled') and OPNsense.HAProxy.general.enabled|default("0") == "1" %}YES{% else %}NO{% endif %}"
haproxy_pidfile="/var/run/haproxy.pid"
haproxy_config="/usr/local/etc/haproxy.conf"
# haproxy_flags=""