diff --git a/net/haproxy/+POST_DEINSTALL b/net/haproxy/+POST_DEINSTALL new file mode 100644 index 0000000000..e69de29bb2 diff --git a/net/haproxy/+POST_INSTALL b/net/haproxy/+POST_INSTALL new file mode 100644 index 0000000000..92d649b8a4 --- /dev/null +++ b/net/haproxy/+POST_INSTALL @@ -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 diff --git a/net/haproxy/+PRE_DEINSTALL b/net/haproxy/+PRE_DEINSTALL new file mode 100644 index 0000000000..e69de29bb2 diff --git a/net/haproxy/+PRE_INSTALL b/net/haproxy/+PRE_INSTALL new file mode 100644 index 0000000000..e69de29bb2 diff --git a/net/haproxy/Makefile b/net/haproxy/Makefile new file mode 100644 index 0000000000..da6899bad0 --- /dev/null +++ b/net/haproxy/Makefile @@ -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" diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/ServiceController.php b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/ServiceController.php new file mode 100644 index 0000000000..251f8166ce --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/ServiceController.php @@ -0,0 +1,168 @@ +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); + } +} diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/SettingsController.php b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/SettingsController.php new file mode 100644 index 0000000000..06ee4407ab --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/SettingsController.php @@ -0,0 +1,1009 @@ +"failed","validations" => array()); + // perform validation + $valMsgs = $mdl->performValidation(); + foreach ($valMsgs as $field => $msg) { + // replace absolute path to attribute for relative one at uuid. + if ($node != null) { + $fieldnm = str_replace($node->__reference, $reference, $msg->getField()); + $result["validations"][$fieldnm] = $msg->getMessage(); + } else { + $result["validations"][$msg->getField()] = $msg->getMessage(); + } + } + + // serialize model to config and save when there are no validation errors + if (count($result['validations']) == 0) { + // save config if validated correctly + $mdl->serializeToConfig(); + + Config::getInstance()->save(); + $result = array("result" => "saved"); + } + + return $result; + } + + /** + * retrieve haproxy settings + * @return array + */ + public function getAction() + { + $result = array(); + if ($this->request->isGet()) { + $mdlProxy = new HAProxy(); + $result['haproxy'] = $mdlProxy->getNodes(); + } + + return $result; + } + + /** + * update haproxy configuration fields + * @return array + * @throws \Phalcon\Validation\Exception + */ + public function setAction() + { + $result = array("result"=>"failed"); + if ($this->request->hasPost("haproxy")) { + // load model and update with provided data + $mdlProxy = new HAProxy(); + $mdlProxy->setNodes($this->request->getPost("haproxy")); + + // perform validation + $valMsgs = $mdlProxy->performValidation(); + foreach ($valMsgs as $field => $msg) { + if (!array_key_exists("validations", $result)) { + $result["validations"] = array(); + } + $result["validations"]["haproxy.".$msg->getField()] = $msg->getMessage(); + } + + // serialize model to config and save + if ($valMsgs->count() == 0) { + $mdlProxy->serializeToConfig(); + $cnf = Config::getInstance(); + $cnf->save(); + $result["result"] = "saved"; + } + + } + + return $result; + } + + /** + * retrieve frontend settings or return defaults + * @param $uuid item unique id + * @return array + */ + public function getFrontendAction($uuid = null) + { + $mdlCP = new HAProxy(); + if ($uuid != null) { + $node = $mdlCP->getNodeByReference('frontends.frontend.'.$uuid); + if ($node != null) { + // return node + return array("frontend" => $node->getNodes()); + } + } else { + // generate new node, but don't save to disc + $node = $mdlCP->frontends->frontend->add() ; + return array("frontend" => $node->getNodes()); + } + return array(); + } + + /** + * update frontend with given properties + * @param $uuid item unique id + * @return array + */ + public function setFrontendAction($uuid) + { + if ($this->request->isPost() && $this->request->hasPost("frontend")) { + $mdlCP = new HAProxy(); + if ($uuid != null) { + $node = $mdlCP->getNodeByReference('frontends.frontend.'.$uuid); + if ($node != null) { + $node->setNodes($this->request->getPost("frontend")); + return $this->save($mdlCP, $node, "frontend"); + } + } + } + return array("result"=>"failed"); + } + + /** + * add new frontend and set with attributes from post + * @return array + */ + public function addFrontendAction() + { + $result = array("result"=>"failed"); + if ($this->request->isPost() && $this->request->hasPost("frontend")) { + $mdlCP = new HAProxy(); + $node = $mdlCP->frontends->frontend->Add(); + $node->setNodes($this->request->getPost("frontend")); + return $this->save($mdlCP, $node, "frontend"); + } + return $result; + } + + /** + * delete frontend by uuid + * @param $uuid item unique id + * @return array status + */ + public function delFrontendAction($uuid) + { + $result = array("result"=>"failed"); + if ($this->request->isPost()) { + $mdlCP = new HAProxy(); + if ($uuid != null) { + if ($mdlCP->frontends->frontend->del($uuid)) { + // if item is removed, serialize to config and save + $mdlCP->serializeToConfig(); + Config::getInstance()->save(); + $result['result'] = 'deleted'; + } else { + $result['result'] = 'not found'; + } + } + } + return $result; + } + + /** + * toggle frontend by uuid (enable/disable) + * @param $uuid item unique id + * @param $enabled desired state enabled(1)/disabled(0), leave empty for toggle + * @return array status + */ + public function toggleFrontendAction($uuid, $enabled = null) + { + + $result = array("result" => "failed"); + if ($this->request->isPost()) { + $mdlCP = new HAProxy(); + if ($uuid != null) { + $node = $mdlCP->getNodeByReference('frontends.frontend.' . $uuid); + if ($node != null) { + if ($enabled == "0" || $enabled == "1") { + $node->enabled = (string)$enabled; + } elseif ((string)$node->enabled == "1") { + $node->enabled = "0"; + } else { + $node->enabled = "1"; + } + $result['result'] = $node->enabled; + // if item has toggled, serialize to config and save + $mdlCP->serializeToConfig(); + Config::getInstance()->save(); + } + } + } + return $result; + } + + /** + * search haproxy frontends + * @return array + */ + public function searchFrontendsAction() + { + $this->sessionClose(); + $mdlCP = new HAProxy(); + $grid = new UIModelGrid($mdlCP->frontends->frontend); + return $grid->fetchBindRequest( + $this->request, + array("enabled", "name", "description","frontendid"), + "name" + ); + } + + /** + * retrieve backend settings or return defaults + * @param $uuid item unique id + * @return array + */ + public function getBackendAction($uuid = null) + { + $mdlCP = new HAProxy(); + if ($uuid != null) { + $node = $mdlCP->getNodeByReference('backends.backend.'.$uuid); + if ($node != null) { + // return node + return array("backend" => $node->getNodes()); + } + } else { + // generate new node, but don't save to disc + $node = $mdlCP->backends->backend->add() ; + return array("backend" => $node->getNodes()); + } + return array(); + } + + /** + * update backend with given properties + * @param $uuid item unique id + * @return array + */ + public function setBackendAction($uuid) + { + if ($this->request->isPost() && $this->request->hasPost("backend")) { + $mdlCP = new HAProxy(); + if ($uuid != null) { + $node = $mdlCP->getNodeByReference('backends.backend.'.$uuid); + if ($node != null) { + $node->setNodes($this->request->getPost("backend")); + return $this->save($mdlCP, $node, "backend"); + } + } + } + return array("result"=>"failed"); + } + + /** + * add new backend and set with attributes from post + * @return array + */ + public function addBackendAction() + { + $result = array("result"=>"failed"); + if ($this->request->isPost() && $this->request->hasPost("backend")) { + $mdlCP = new HAProxy(); + $node = $mdlCP->backends->backend->Add(); + $node->setNodes($this->request->getPost("backend")); + return $this->save($mdlCP, $node, "backend"); + } + return $result; + } + + /** + * delete backend by uuid + * @param $uuid item unique id + * @return array status + */ + public function delBackendAction($uuid) + { + $result = array("result"=>"failed"); + if ($this->request->isPost()) { + $mdlCP = new HAProxy(); + if ($uuid != null) { + if ($mdlCP->backends->backend->del($uuid)) { + // if item is removed, serialize to config and save + $mdlCP->serializeToConfig(); + Config::getInstance()->save(); + $result['result'] = 'deleted'; + } else { + $result['result'] = 'not found'; + } + } + } + return $result; + } + + /** + * toggle backend by uuid (enable/disable) + * @param $uuid item unique id + * @param $enabled desired state enabled(1)/disabled(0), leave empty for toggle + * @return array status + */ + public function toggleBackendAction($uuid, $enabled = null) + { + + $result = array("result" => "failed"); + if ($this->request->isPost()) { + $mdlCP = new HAProxy(); + if ($uuid != null) { + $node = $mdlCP->getNodeByReference('backends.backend.' . $uuid); + if ($node != null) { + if ($enabled == "0" || $enabled == "1") { + $node->enabled = (string)$enabled; + } elseif ((string)$node->enabled == "1") { + $node->enabled = "0"; + } else { + $node->enabled = "1"; + } + $result['result'] = $node->enabled; + // if item has toggled, serialize to config and save + $mdlCP->serializeToConfig(); + Config::getInstance()->save(); + } + } + } + return $result; + } + + /** + * search haproxy backends + * @return array + */ + public function searchBackendsAction() + { + $this->sessionClose(); + $mdlCP = new HAProxy(); + $grid = new UIModelGrid($mdlCP->backends->backend); + return $grid->fetchBindRequest( + $this->request, + array("enabled", "name", "description", "backendid"), + "name" + ); + } + + /** + * retrieve server settings or return defaults + * @param $uuid item unique id + * @return array + */ + public function getServerAction($uuid = null) + { + $mdlCP = new HAProxy(); + if ($uuid != null) { + $node = $mdlCP->getNodeByReference('servers.server.'.$uuid); + if ($node != null) { + // return node + return array("server" => $node->getNodes()); + } + } else { + // generate new node, but don't save to disc + $node = $mdlCP->servers->server->add() ; + return array("server" => $node->getNodes()); + } + return array(); + } + + /** + * update server with given properties + * @param $uuid item unique id + * @return array + */ + public function setServerAction($uuid) + { + if ($this->request->isPost() && $this->request->hasPost("server")) { + $mdlCP = new HAProxy(); + if ($uuid != null) { + $node = $mdlCP->getNodeByReference('servers.server.'.$uuid); + if ($node != null) { + $node->setNodes($this->request->getPost("server")); + return $this->save($mdlCP, $node, "server"); + } + } + } + return array("result"=>"failed"); + } + + /** + * add new server and set with attributes from post + * @return array + */ + public function addServerAction() + { + $result = array("result"=>"failed"); + if ($this->request->isPost() && $this->request->hasPost("server")) { + $mdlCP = new HAProxy(); + $node = $mdlCP->servers->server->Add(); + $node->setNodes($this->request->getPost("server")); + return $this->save($mdlCP, $node, "server"); + } + return $result; + } + + /** + * delete server by uuid + * @param $uuid item unique id + * @return array status + */ + public function delServerAction($uuid) + { + $result = array("result"=>"failed"); + if ($this->request->isPost()) { + $mdlCP = new HAProxy(); + if ($uuid != null) { + if ($mdlCP->servers->server->del($uuid)) { + // if item is removed, serialize to config and save + $mdlCP->serializeToConfig(); + Config::getInstance()->save(); + $result['result'] = 'deleted'; + } else { + $result['result'] = 'not found'; + } + } + } + return $result; + } + + /** + * search servers + * @return array + */ + public function searchServersAction() + { + $this->sessionClose(); + $mdlCP = new HAProxy(); + $grid = new UIModelGrid($mdlCP->servers->server); + return $grid->fetchBindRequest( + $this->request, + array("name", "address", "port", "description", "serverid"), + "name" + ); + } + + /** + * retrieve healthcheck settings or return defaults + * @param $uuid item unique id + * @return array + */ + public function getHealthcheckAction($uuid = null) + { + $mdlCP = new HAProxy(); + if ($uuid != null) { + $node = $mdlCP->getNodeByReference('healthchecks.healthcheck.'.$uuid); + if ($node != null) { + // return node + return array("healthcheck" => $node->getNodes()); + } + } else { + // generate new node, but don't save to disc + $node = $mdlCP->healthchecks->healthcheck->add() ; + return array("healthcheck" => $node->getNodes()); + } + return array(); + } + + /** + * update healthcheck with given properties + * @param $uuid item unique id + * @return array + */ + public function setHealthcheckAction($uuid) + { + if ($this->request->isPost() && $this->request->hasPost("healthcheck")) { + $mdlCP = new HAProxy(); + if ($uuid != null) { + $node = $mdlCP->getNodeByReference('healthchecks.healthcheck.'.$uuid); + if ($node != null) { + $node->setNodes($this->request->getPost("healthcheck")); + return $this->save($mdlCP, $node, "healthcheck"); + } + } + } + return array("result"=>"failed"); + } + + /** + * add new healthcheck and set with attributes from post + * @return array + */ + public function addHealthcheckAction() + { + $result = array("result"=>"failed"); + if ($this->request->isPost() && $this->request->hasPost("healthcheck")) { + $mdlCP = new HAProxy(); + $node = $mdlCP->healthchecks->healthcheck->Add(); + $node->setNodes($this->request->getPost("healthcheck")); + return $this->save($mdlCP, $node, "healthcheck"); + } + return $result; + } + + /** + * delete healthcheck by uuid + * @param $uuid item unique id + * @return array status + */ + public function delHealthcheckAction($uuid) + { + $result = array("result"=>"failed"); + if ($this->request->isPost()) { + $mdlCP = new HAProxy(); + if ($uuid != null) { + if ($mdlCP->healthchecks->healthcheck->del($uuid)) { + // if item is removed, serialize to config and save + $mdlCP->serializeToConfig(); + Config::getInstance()->save(); + $result['result'] = 'deleted'; + } else { + $result['result'] = 'not found'; + } + } + } + return $result; + } + + /** + * search healthchecks + * @return array + */ + public function searchHealthchecksAction() + { + $this->sessionClose(); + $mdlCP = new HAProxy(); + $grid = new UIModelGrid($mdlCP->healthchecks->healthcheck); + return $grid->fetchBindRequest( + $this->request, + array("name", "description", "healthcheckid"), + "name" + ); + } + + /** + * retrieve acl settings or return defaults + * @param $uuid item unique id + * @return array + */ + public function getAclAction($uuid = null) + { + $mdlCP = new HAProxy(); + if ($uuid != null) { + $node = $mdlCP->getNodeByReference('acls.acl.'.$uuid); + if ($node != null) { + // return node + return array("acl" => $node->getNodes()); + } + } else { + // generate new node, but don't save to disc + $node = $mdlCP->acls->acl->add() ; + return array("acl" => $node->getNodes()); + } + return array(); + } + + /** + * update acl with given properties + * @param $uuid item unique id + * @return array + */ + public function setAclAction($uuid) + { + if ($this->request->isPost() && $this->request->hasPost("acl")) { + $mdlCP = new HAProxy(); + if ($uuid != null) { + $node = $mdlCP->getNodeByReference('acls.acl.'.$uuid); + if ($node != null) { + $node->setNodes($this->request->getPost("acl")); + return $this->save($mdlCP, $node, "acl"); + } + } + } + return array("result"=>"failed"); + } + + /** + * add new acl and set with attributes from post + * @return array + */ + public function addAclAction() + { + $result = array("result"=>"failed"); + if ($this->request->isPost() && $this->request->hasPost("acl")) { + $mdlCP = new HAProxy(); + $node = $mdlCP->acls->acl->Add(); + $node->setNodes($this->request->getPost("acl")); + return $this->save($mdlCP, $node, "acl"); + } + return $result; + } + + /** + * delete acl by uuid + * @param $uuid item unique id + * @return array status + */ + public function delAclAction($uuid) + { + $result = array("result"=>"failed"); + if ($this->request->isPost()) { + $mdlCP = new HAProxy(); + if ($uuid != null) { + if ($mdlCP->acls->acl->del($uuid)) { + // if item is removed, serialize to config and save + $mdlCP->serializeToConfig(); + Config::getInstance()->save(); + $result['result'] = 'deleted'; + } else { + $result['result'] = 'not found'; + } + } + } + return $result; + } + + /** + * search acls + * @return array + */ + public function searchAclsAction() + { + $this->sessionClose(); + $mdlCP = new HAProxy(); + $grid = new UIModelGrid($mdlCP->acls->acl); + return $grid->fetchBindRequest( + $this->request, + array("name", "description", "aclid"), + "name" + ); + } + + /** + * retrieve action settings or return defaults + * @param $uuid item unique id + * @return array + */ + public function getActionAction($uuid = null) + { + $mdlCP = new HAProxy(); + if ($uuid != null) { + $node = $mdlCP->getNodeByReference('actions.action.'.$uuid); + if ($node != null) { + // return node + return array("action" => $node->getNodes()); + } + } else { + // generate new node, but don't save to disc + $node = $mdlCP->actions->action->add() ; + return array("action" => $node->getNodes()); + } + return array(); + } + + /** + * update action with given properties + * @param $uuid item unique id + * @return array + */ + public function setActionAction($uuid) + { + if ($this->request->isPost() && $this->request->hasPost("action")) { + $mdlCP = new HAProxy(); + if ($uuid != null) { + $node = $mdlCP->getNodeByReference('actions.action.'.$uuid); + if ($node != null) { + $node->setNodes($this->request->getPost("action")); + return $this->save($mdlCP, $node, "action"); + } + } + } + return array("result"=>"failed"); + } + + /** + * add new action and set with attributes from post + * @return array + */ + public function addActionAction() + { + $result = array("result"=>"failed"); + if ($this->request->isPost() && $this->request->hasPost("action")) { + $mdlCP = new HAProxy(); + $node = $mdlCP->actions->action->Add(); + $node->setNodes($this->request->getPost("action")); + return $this->save($mdlCP, $node, "action"); + } + return $result; + } + + /** + * delete action by uuid + * @param $uuid item unique id + * @return array status + */ + public function delActionAction($uuid) + { + $result = array("result"=>"failed"); + if ($this->request->isPost()) { + $mdlCP = new HAProxy(); + if ($uuid != null) { + if ($mdlCP->actions->action->del($uuid)) { + // if item is removed, serialize to config and save + $mdlCP->serializeToConfig(); + Config::getInstance()->save(); + $result['result'] = 'deleted'; + } else { + $result['result'] = 'not found'; + } + } + } + return $result; + } + + /** + * search actions + * @return array + */ + public function searchActionsAction() + { + $this->sessionClose(); + $mdlCP = new HAProxy(); + $grid = new UIModelGrid($mdlCP->actions->action); + return $grid->fetchBindRequest( + $this->request, + array("name", "description", "actionid"), + "name" + ); + } + + /** + * retrieve lua settings or return defaults + * @param $uuid item unique id + * @return array + */ + public function getLuaAction($uuid = null) + { + $mdlCP = new HAProxy(); + if ($uuid != null) { + $node = $mdlCP->getNodeByReference('luas.lua.'.$uuid); + if ($node != null) { + // return node + return array("lua" => $node->getNodes()); + } + } else { + // generate new node, but don't save to disc + $node = $mdlCP->luas->lua->add() ; + return array("lua" => $node->getNodes()); + } + return array(); + } + + /** + * update lua with given properties + * @param $uuid item unique id + * @return array + */ + public function setLuaAction($uuid) + { + if ($this->request->isPost() && $this->request->hasPost("lua")) { + $mdlCP = new HAProxy(); + if ($uuid != null) { + $node = $mdlCP->getNodeByReference('luas.lua.'.$uuid); + if ($node != null) { + $node->setNodes($this->request->getPost("lua")); + return $this->save($mdlCP, $node, "lua"); + } + } + } + return array("result"=>"failed"); + } + + /** + * add new lua and set with attributes from post + * @return array + */ + public function addLuaAction() + { + $result = array("result"=>"failed"); + if ($this->request->isPost() && $this->request->hasPost("lua")) { + $mdlCP = new HAProxy(); + $node = $mdlCP->luas->lua->Add(); + $node->setNodes($this->request->getPost("lua")); + return $this->save($mdlCP, $node, "lua"); + } + return $result; + } + + /** + * delete lua by uuid + * @param $uuid item unique id + * @return array status + */ + public function delLuaAction($uuid) + { + $result = array("result"=>"failed"); + if ($this->request->isPost()) { + $mdlCP = new HAProxy(); + if ($uuid != null) { + if ($mdlCP->luas->lua->del($uuid)) { + // if item is removed, serialize to config and save + $mdlCP->serializeToConfig(); + Config::getInstance()->save(); + $result['result'] = 'deleted'; + } else { + $result['result'] = 'not found'; + } + } + } + return $result; + } + + /** + * toggle lua by uuid (enable/disable) + * @param $uuid item unique id + * @param $enabled desired state enabled(1)/disabled(0), leave empty for toggle + * @return array status + */ + public function toggleLuaAction($uuid, $enabled = null) + { + + $result = array("result" => "failed"); + if ($this->request->isPost()) { + $mdlCP = new HAProxy(); + if ($uuid != null) { + $node = $mdlCP->getNodeByReference('luas.lua.' . $uuid); + if ($node != null) { + if ($enabled == "0" || $enabled == "1") { + $node->enabled = (string)$enabled; + } elseif ((string)$node->enabled == "1") { + $node->enabled = "0"; + } else { + $node->enabled = "1"; + } + $result['result'] = $node->enabled; + // if item has toggled, serialize to config and save + $mdlCP->serializeToConfig(); + Config::getInstance()->save(); + } + } + } + return $result; + } + + /** + * search luas + * @return array + */ + public function searchLuasAction() + { + $this->sessionClose(); + $mdlCP = new HAProxy(); + $grid = new UIModelGrid($mdlCP->luas->lua); + return $grid->fetchBindRequest( + $this->request, + array("enabled", "name", "description", "luaid"), + "name" + ); + } + + /** + * retrieve errorfile settings or return defaults + * @param $uuid item unique id + * @return array + */ + public function getErrorfileAction($uuid = null) + { + $mdlCP = new HAProxy(); + if ($uuid != null) { + $node = $mdlCP->getNodeByReference('errorfiles.errorfile.'.$uuid); + if ($node != null) { + // return node + return array("errorfile" => $node->getNodes()); + } + } else { + // generate new node, but don't save to disc + $node = $mdlCP->errorfiles->errorfile->add() ; + return array("errorfile" => $node->getNodes()); + } + return array(); + } + + /** + * update errorfile with given properties + * @param $uuid item unique id + * @return array + */ + public function setErrorfileAction($uuid) + { + if ($this->request->isPost() && $this->request->hasPost("errorfile")) { + $mdlCP = new HAProxy(); + if ($uuid != null) { + $node = $mdlCP->getNodeByReference('errorfiles.errorfile.'.$uuid); + if ($node != null) { + $node->setNodes($this->request->getPost("errorfile")); + return $this->save($mdlCP, $node, "errorfile"); + } + } + } + return array("result"=>"failed"); + } + + /** + * add new errorfile and set with attributes from post + * @return array + */ + public function addErrorfileAction() + { + $result = array("result"=>"failed"); + if ($this->request->isPost() && $this->request->hasPost("errorfile")) { + $mdlCP = new HAProxy(); + $node = $mdlCP->errorfiles->errorfile->Add(); + $node->setNodes($this->request->getPost("errorfile")); + return $this->save($mdlCP, $node, "errorfile"); + } + return $result; + } + + /** + * delete errorfile by uuid + * @param $uuid item unique id + * @return array status + */ + public function delErrorfileAction($uuid) + { + $result = array("result"=>"failed"); + if ($this->request->isPost()) { + $mdlCP = new HAProxy(); + if ($uuid != null) { + if ($mdlCP->errorfiles->errorfile->del($uuid)) { + // if item is removed, serialize to config and save + $mdlCP->serializeToConfig(); + Config::getInstance()->save(); + $result['result'] = 'deleted'; + } else { + $result['result'] = 'not found'; + } + } + } + return $result; + } + + /** + * search errorfiles + * @return array + */ + public function searchErrorfilesAction() + { + $this->sessionClose(); + $mdlCP = new HAProxy(); + $grid = new UIModelGrid($mdlCP->errorfiles->errorfile); + return $grid->fetchBindRequest( + $this->request, + array("name", "description", "errorfileid"), + "name" + ); + } +} diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/StatisticsController.php b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/StatisticsController.php new file mode 100644 index 0000000000..47c3ed6d1b --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/StatisticsController.php @@ -0,0 +1,77 @@ +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; + } +} diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/IndexController.php b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/IndexController.php new file mode 100644 index 0000000000..8e02527783 --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/IndexController.php @@ -0,0 +1,59 @@ +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'); + } +} diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/StatisticsController.php b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/StatisticsController.php new file mode 100644 index 0000000000..b0814052fd --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/StatisticsController.php @@ -0,0 +1,44 @@ +view->title = "HAProxy Load Balancer / Statistics"; + // choose template + $this->view->pick('OPNsense/HAProxy/statistics'); + } +} diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAcl.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAcl.xml new file mode 100644 index 0000000000..34579e5725 --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAcl.xml @@ -0,0 +1,52 @@ +
+ + acl.name + + text + Name to identify this ACL. + + + acl.description + + text + Description for this ACL. + + + + header + + + acl.expression + + dropdown + Select ACL expression. + + + acl.negate + + checkbox + + + + acl.value + + text + + + + + header + + + acl.urlparam + + text + Not used for any other expression.]]> + + + acl.queryBackend + + dropdown + Not used for any other expression.]]> + +
diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAction.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAction.xml new file mode 100644 index 0000000000..96e7f306b9 --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAction.xml @@ -0,0 +1,81 @@ +
+ + action.name + + text + Name to identify this action. + + + action.description + + text + Description for this action. + + + + header + + + action.testType + + dropdown + + + + action.linkedAcls + + select_multiple + + + + + action.operator + + dropdown + + + + action.type + + dropdown + + + + + header + + + action.useBackend + + dropdown + Not used for any other action.]]> + + + action.useServer + + dropdown + Not used for any other action.]]> + + + + header + + + action.actionName + + text + + + + action.actionFind + + text + + + + action.actionValue + + text + + +
diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogBackend.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogBackend.xml new file mode 100644 index 0000000000..d812696fb0 --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogBackend.xml @@ -0,0 +1,157 @@ +
+ + backend.enabled + + checkbox + Enable this backend + + + backend.name + + text + Name to identify this backend. + + + backend.description + + text + Description for this backend. + + + backend.mode + + dropdown + + Set the same mode for backend and frontend. + + + backend.algorithm + + dropdown + HAProxy documentation for a full description.]]> + Choose a load balancing algorithm. + + + backend.linkedServers + + select_multiple + + true + + Type server name or choose from list. + + + + header + + + backend.healthCheckEnabled + + checkbox + + + + backend.healthCheck + + dropdown + + + + backend.healthCheckLogStatus + + checkbox + + + + + header + + + backend.stickiness_pattern + + dropdown + HAProxy documentation for a full description.
NOTE: Consider not using this feature in multi-process mode, it can result in random behaviours.
]]>
+ Choose a persistence type. +
+ + backend.stickiness_expire + + text + + true + + + backend.stickiness_size + + text + + true + + + backend.stickiness_cookiename + + text + + + + backend.stickiness_cookielength + + text + + + + + header + + + backend.tuning_timeoutConnect + + text + + true + + + backend.tuning_timeoutServer + + text + + true + + + backend.tuning_retries + + text + + + + backend.customOptions + + textbox +
NOTE: The syntax will not be checked, use at your own risk!
]]>
+ true +
+ + + header + + + backend.linkedActions + + select_multiple + + + Choose actions. + + + + header + + + backend.linkedErrorfiles + + select_multiple + + + Choose error files. + +
diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogErrorfile.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogErrorfile.xml new file mode 100644 index 0000000000..c8b2b352d6 --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogErrorfile.xml @@ -0,0 +1,26 @@ +
+ + errorfile.name + + text + Name to identify this error file. + + + errorfile.description + + text + Description for this error file. + + + errorfile.code + + dropdown + The HTTP status code. + + + errorfile.content + + textbox + Paste the content of your errorfile here. The files should not exceed the configured buffer size, which generally is 8 or 16 kB. + +
diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogFrontend.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogFrontend.xml new file mode 100644 index 0000000000..9f04d755cf --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogFrontend.xml @@ -0,0 +1,170 @@ +
+ + frontend.enabled + + checkbox + Enable this frontend + + + frontend.name + + text + Name to identify this frontend. + + + frontend.description + + text + Description for this frontend. + + + frontend.bind + + select_multiple + + true + + Enter address:port here. Finish with TAB. + + + frontend.mode + + dropdown + + + + frontend.defaultBackend + + dropdown + + + + + header + + + frontend.ssl_enabled + + checkbox + Enable SSL offloading + + + frontend.ssl_certificates + + select_multiple + + true + To import additional certificates, go to Certificate Manager.]]> + Type certificate name or choose from list. + + + frontend.ssl_customOptions + + text + Example: no-sslv3 ciphers HIGH:!DSS:!aNULL@STRENGTH
NOTE: The syntax will not be checked, use at your own risk!
]]>
+ true +
+ + + header + + + frontend.tuning_maxConnections + + text + + + + frontend.tuning_timeoutClient + + text + + true + + + + header + + + frontend.logging_dontLogNull + + checkbox + + true + + + frontend.logging_dontLogNormal + + checkbox + + true + + + frontend.logging_logSeparateErrors + + checkbox + + true + + + frontend.logging_detailedLog + + checkbox + + + + frontend.logging_socketStats + + checkbox + + true + + + + header + + + frontend.forwardFor + + checkbox + + + + frontend.connectionBehaviour + + dropdown + keep-alive mode with regards to persistent connections. Option "http-tunnel" disables any HTTP processing past the first request and the first response. Option "httpclose" 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 "http-server-close" 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 "forceclose" HAProxy will actively close the outgoing server channel as soon as the server has finished to respond and release some resources earlier.]]> + true + + + frontend.customOptions + + textbox +
NOTE: The syntax will not be checked, use at your own risk!
]]>
+ true +
+ + + header + + + frontend.linkedActions + + select_multiple + + + Choose actions. + + + + header + + + frontend.linkedErrorfiles + + select_multiple + + + Choose error files. + +
diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogHealthcheck.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogHealthcheck.xml new file mode 100644 index 0000000000..27b397f94e --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogHealthcheck.xml @@ -0,0 +1,106 @@ +
+ + healthcheck.name + + text + Name to identify this ACL. + + + healthcheck.description + + text + Description for this ACL. + + + healthcheck.type + + dropdown + + + + healthcheck.interval + + text + + + + + header + + + healthcheck.http_method + + dropdown + + + + healthcheck.http_uri + + text + + + + healthcheck.http_version + + dropdown + + + + healthcheck.http_host + + text + + + + + header + + + healthcheck.http_expressionEnabled + + checkbox + + + healthcheck.http_expression + + dropdown + + + + healthcheck.http_negate + + checkbox + + + + healthcheck.http_value + + text +
NOTE: 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.
]]>
+ + Example: no-sslv3 ciphers HIGH:!DSS:!aNULL@STRENGTH
NOTE: The syntax will not be checked, use at your own risk!
]]>
+ +
+ + + header + + + healthcheck.agentPort + + text + + + + healthcheck.dbUser + + text + + + + healthcheck.smtpDomain + + text + + +
diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogLua.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogLua.xml new file mode 100644 index 0000000000..72a791e424 --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogLua.xml @@ -0,0 +1,26 @@ +
+ + lua.enabled + + checkbox + Enable this Lua script. + + + lua.name + + text + Name to identify this Lua script. + + + lua.description + + text + Description for this Lua script. + + + lua.content + + textbox + Paste the content of your Lua script here. + +
diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogServer.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogServer.xml new file mode 100644 index 0000000000..1a399cb2b2 --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogServer.xml @@ -0,0 +1,54 @@ +
+ + server.name + + text + Name to identify this server. + + + server.description + + text + Description for this server. + + + server.address + + text + + Enter server address. + + + server.port + + text + + + + server.mode + + dropdown + + true + + + server.ssl + + checkbox + + + + server.weight + + text + + true + + + server.checkInterval + + text + + true + +
diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/main.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/main.xml new file mode 100644 index 0000000000..94c57028c4 --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/main.xml @@ -0,0 +1,190 @@ +
+ + + + + info + + + haproxy.general.enabled + + checkbox + Enable or disable the HAProxy service. + + + + + + info + + + haproxy.general.tuning.nbproc + + text +
NOTE: You may experience random issues in multi-process mode. For more information about the "nbproc" option please see the HAProxy Documentation.
]]>
+ true +
+ + haproxy.general.tuning.maxConnections + + text +
NOTE: 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.
]]>
+
+ + haproxy.general.tuning.maxDHSize + + text +
NOTE: Higher values will increase the CPU load. For more information about the "tune.ssl.default-dh-param" option please see the HAProxy Documentation.
]]>
+
+ + haproxy.general.tuning.bufferSize + + text +
NOTE: 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.
]]>
+ true +
+ + haproxy.general.tuning.checkBufferSize + + text + + true + + + haproxy.general.tuning.luaMaxMem + + text + + true + + + haproxy.general.tuning.spreadChecks + + text + + + + haproxy.general.tuning.customOptions + + textbox +
NOTE: The syntax will not be checked, use at your own risk!
]]>
+ true +
+
+ + + + info + + + haproxy.general.defaults.maxConnections + + text + + + + haproxy.general.defaults.timeoutClient + + text + + + + haproxy.general.defaults.timeoutConnect + + text + + + + haproxy.general.defaults.timeoutServer + + text + + + + haproxy.general.defaults.retries + + text + + + + haproxy.general.defaults.redispatch + + dropdown + + + + + + haproxy.general.logging.host + + text + + + + haproxy.general.logging.facility + + dropdown + + + + haproxy.general.logging.level + + dropdown + + + + haproxy.general.logging.length + + text + + true + + + + + haproxy.general.stats.enabled + + checkbox + + + + haproxy.general.stats.port + + text + + true + + + haproxy.general.stats.remoteEnabled + + checkbox + This may be a security risk if you do not enable authentication! Note that you need to add appropiate firewall rules for this to work.]]> + + + haproxy.general.stats.remoteBind + + select_multiple + + true + + Enter address:port here. Finish with TAB. + + + haproxy.general.stats.authEnabled + + checkbox + + + haproxy.general.stats.users + + select_multiple + + true + + Enter user:password here. Finish with TAB. + + +
+ + haproxy-general-settings +
diff --git a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/ACL/ACL.xml b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/ACL/ACL.xml new file mode 100644 index 0000000000..f03a9221d8 --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/ACL/ACL.xml @@ -0,0 +1,11 @@ + + + + WebCfg - Services: HAProxy page + Allow access to the 'Services: HAProxy' page. + + ui/haproxy/* + api/haproxy/* + + + diff --git a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.php b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.php new file mode 100644 index 0000000000..f5526b04ab --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.php @@ -0,0 +1,83 @@ +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; + } +} diff --git a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml new file mode 100644 index 0000000000..74ae5cbbef --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml @@ -0,0 +1,901 @@ + + //OPNsense/HAProxy + + the HAProxy load balancer + + + + + 0 + Y + + + + 1 + 500000 + Please specify a value between 1 and 500000. + N + + + 1 + 1 + 128 + Please specify a value between 1 and 128. + Y + + + 1024 + 1024 + 16384 + Please specify a value between 1024 and 16384. + Y + + + 16384 + 1024 + 1048576 + Please specify a value between 1024 and 1048576. + N + + + 16384 + 1024 + 1048576 + Please specify a value between 1024 and 1048576. + N + + + 0 + 0 + 50 + Please specify a value between 0 and 50. + Y + + + 0 + 0 + 1024 + Please specify a value between 0 and 1024. + N + + + N + + + + + 1 + 500000 + Please specify a value between 1 and 500000. + N + + + 1 + 1000000 + 30000 + Please specify a value between 1 and 1000000. + N + + + 100 + 1000000 + 30000 + Please specify a value between 100 and 1000000. + N + + + 100 + 1000000 + 30000 + Please specify a value between 100 and 1000000. + N + + + 1 + 100 + 3 + Please specify a value between 1 and 100. + Y + + + N + x-1 + + redispatch on every 3rd retry + redispatch on every 2nd retry + redispatch on every retry + Disable redispatching + redispatch on the last retry [default] + redispatch on the 2nd retry prior to the last retry + redispatch on the 3rd retry prior to the last retry + + + + + + 127.0.0.1 + /^((([0-9a-zA-Z._\-\*]+:[0-9]+)([,]){0,1}))*/u + lower + Please provide a valid host, i.e. 127.0.0.1 or 10.0.0.1:514. + Y + + + Y + local0 + + alert + audit + auth2 + auth + cron2 + cron + daemon + ftp + kern + local0 + local1 + local2 + local3 + local4 + local5 + local6 + local7 + lpr + mail + news + ntp + syslog + user + uucp + + + + N + + alert + crit + debug + emerg + err + info + notice + warning + + + + 64 + 65535 + Please specify a value between 64 and 65535. + N + + + + + 0 + N + + + 1024 + 65535 + 8822 + Please specify a value between 1024 and 65535. + Y + + + 0 + N + + + + N + Y + /^((([0-9a-zA-Z._\-]+:[0-9a-zA-Z._\-]+)([,]){0,1}))*/u + lower + Please provide a valid listen address, i.e. 10.0.0.1:8080 or haproxy.example.com:8999. + + + 0 + N + + + N + Y + /^((([0-9a-zA-Z._\-]+:[0-9a-zA-Z._\-]+)([,]){0,1}))*/u + Please provide a valid user and password, i.e. user:secret123. + + + + + + + N + + + 1 + Y + + + Y + /^([0-9a-zA-Z._]){1,255}$/u + Should be a string between 1 and 255 characters. + + + N + /^([\t\n\v\f\r 0-9a-zA-Z.:\-,_()\x{00A0}-\x{FFFF}]){1,255}$/u + Should be a string between 1 and 255 characters. + + + Y + Y + + /^((([0-9a-zA-Z._\-\*]+:[0-9]+)([,]){0,1}))*/u + lower + Please provide a valid listen address, i.e. 127.0.0.1:8080 or www.example.com:443. + + + Y + http + + HTTP / HTTPS (SSL offloading) [default] + SSL / HTTPS (TCP mode) + TCP + + + + + + + Related item not found + N + + + + 0 + Y + + + N + Y + Please select a valid certificate from the list. + + + N + + + + 1 + 500000 + Please specify a value between 1 and 500000. + N + + + 1 + 1000000 + Please specify a value between 1 and 1000000. + N + + + + 0 + Y + + + 0 + Y + + + 0 + Y + + + 0 + Y + + + 0 + Y + + + 0 + Y + + + Y + http-keep-alive + + http-keep-alive [default] + http-tunnel + httpclose + http-server-close + forceclose + + + + N + + + + + + Related item not found + Y + N + + + + + + Related item not found + Y + N + + + + + + + N + + + 1 + Y + + + Y + /^([0-9a-zA-Z._]){1,255}$/u + Should be a string between 1 and 255 characters. + + + N + /^([\t\n\v\f\r 0-9a-zA-Z.:\-,_()\x{00A0}-\x{FFFF}]){1,255}$/u + Should be a string between 1 and 255 characters. + + + Y + http + + HTTP (Layer 7) [default] + TCP (Layer 4) + + + + Y + source + + Source-IP Hash [default] + Round Robin + Static Round Robin + Least Connections + URI Hash (only HTTP mode) + + + + + + + Related item not found + Y + N + + + 1 + Y + + + + + + Related item not found + N + N + + + 0 + N + + + + N + sourceipv4 + + Stick on Source-IP [default] + Stick on Source-IPv6 + Stick on existing Cookie value + Stick on RDP-Cookie + + + + Y + 30m + /^([0-9]{1,5}[d|h|m|s|ms]{1})*/u + lower + Should be a number between 1 and 5 characters followed by either "d", "h", "m", "s" or "ms". + + + Y + 50k + /^([0-9]{1,5}[k|m|g]{1})*/u + lower + Should be a number between 1 and 5 characters followed by either "k", "m" or "g". + + + N + /^([0-9a-zA-Z\.,_\-:]){1,1024}$/u + Does not look like a valid cookie name (most special characters are not allowed). + + + 1 + 10000 + Please specify a value between 1 and 10000. + N + + + + 100 + 1000000 + Please specify a value between 100 and 1000000. + N + + + 100 + 1000000 + Please specify a value between 100 and 1000000. + N + + + 1 + 100 + Please specify a value between 1 and 100. + N + + + N + + + + + + Related item not found + Y + N + + + + + + Related item not found + Y + N + + + + + + + /^([0-9a-zA-Z._]){1,255}$/u + Should be a string between 1 and 255 characters. + Y + + + /^([\t\n\v\f\r 0-9a-zA-Z.:\-,_()\x{00A0}-\x{FFFF}]){1,255}$/u + Should be a string between 1 and 255 characters. + N + +
+ /^([0-9a-zA-Z\.,_\-:]){0,1024}$/u + Please specify a valid servername or IP address. + Y +
+ + 80 + 1 + 65535 + Please specify a value between 1 and 65535. + Y + + + Y + active + + active [default] + backup + disabled + inactive + + + + 0 + Y + + + 0 + 256 + Please specify a value between 0 and 256. + N + + + 1000 + 500 + 100000 + Please specify a value between 500 and 100000. + Y + +
+
+ + + + /^([0-9a-zA-Z._]){1,255}$/u + Should be a string between 1 and 255 characters. + Y + + + /^([\t\n\v\f\r 0-9a-zA-Z.:\-,_()\x{00A0}-\x{FFFF}]){1,255}$/u + Should be a string between 1 and 255 characters. + N + + + Y + http + + TCP + HTTP [default] + Agent + LDAP + MySQL + PostgreSQL + Redis + SMTP + ESMTP + SSL + + + + 250 + 1000000 + 1000 + Please specify a value between 250 and 1000000. + N + + + + N + options + + OPTIONS [default] + HEAD + GET + PUT + POST + DELETE + TRACE + + + + / + /^(.*){1,255}$/u + Should be a string between 1 and 255 characters. + N + + + N + http10 + + HTTP/1.0 [default] + HTTP/1.1 + + + + localhost + /^([0-9a-zA-Z._\-]){1,255}$/u + Should be a string between 1 and 255 characters. + N + + + 0 + N + + + N + + test the exact string match for the HTTP status code + test a regular expression for the HTTP status code + test the exact string match in the HTTP response body + test a regular expression on the HTTP response body + + + + 0 + N + + + N + + + 1 + 65535 + Please specify a value between 1 and 65535. + N + + + /^([0-9a-zA-Z._\-]){1,255}$/u + Should be a string between 1 and 255 characters. + N + + + /^([0-9a-zA-Z._\-]){1,255}$/u + Should be a string between 1 and 255 characters. + N + + + + + + + N + + + /^([0-9a-zA-Z._]){1,255}$/u + Should be a string between 1 and 255 characters. + Y + + + /^([\t\n\v\f\r 0-9a-zA-Z.:\-,_()\x{00A0}-\x{FFFF}]){1,255}$/u + Should be a string between 1 and 255 characters. + N + + + Y + + Host starts with + Host ends with + Host matches + Host regex + Host contains + Path starts with + Path ends with + Path matches + Path regex + Path contains + URL parameter contains + SSL Client certificate verify error result + SSL Client certificate is valid + SSL Client issued by CA common-name + Source IP matches IP or Alias + Minimum count usable servers + Traffic is http (no value needed) + Traffic is ssl (no value needed) + SNI TLS extension matches + SNI TLS extension contains + SNI TLS extension starts with + SNI TLS extension ends with + SNI TLS extension regex + Custom ACL + + + + 0 + Y + + + N + + + N + + + + + + Related item not found + N + + + + + + + /^([0-9a-zA-Z._]){1,255}$/u + Should be a string between 1 and 255 characters. + Y + + + /^([\t\n\v\f\r 0-9a-zA-Z.:\-,_()\x{00A0}-\x{FFFF}]){1,255}$/u + Should be a string between 1 and 255 characters. + N + + + Y + if + + IF [default] + UNLESS + + + + + + + Related item not found + Y + N + + + N + and + + AND [default] + OR + + + + Y + + Use Backend + Use Server + http-request allow + http-request deny + http-request tarpit + http-request auth + http-request redirect + http-request lua action + http-request lua service + http-request header add + http-request header set + http-request header delete + http-request header replace + http-request header replace value + http-response allow + http-response deny + http-response lua script + http-response header add + http-response header set + http-response header delete + http-response header replace + http-response header replace value + tcp-request connection accept + tcp-request connection reject + tcp-request content accept + tcp-request content reject + tcp-request content lua script + tcp-request content use-service + tcp-response content accept + tcp-response content close + tcp-response content reject + tcp-response content lua script + Custom + + + + + + + Related item not found + Y + N + + + + + + Related item not found + Y + N + + + N + + + N + + + N + + + + + + + Y + + + 1 + Y + + + /^([0-9a-zA-Z_\-]){1,255}$/u + Should be a string between 1 and 255 characters. + Y + + + /^([\t\n\v\f\r 0-9a-zA-Z.:\-,_()\x{00A0}-\x{FFFF}]){1,255}$/u + Should be a string between 1 and 255 characters. + N + + + Y + + + + + + + Y + + + /^([0-9a-zA-Z_\-]){1,255}$/u + Should be a string between 1 and 255 characters. + Y + + + /^([\t\n\v\f\r 0-9a-zA-Z.:\-,_()\x{00A0}-\x{FFFF}]){1,255}$/u + Should be a string between 1 and 255 characters. + N + + + Y + 503 + + 200 + 400 + 403 + 405 + 408 + 429 + 500 + 502 + 503 + 504 + + + + Y + + + +
+
diff --git a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/Menu/Menu.xml b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/Menu/Menu.xml new file mode 100644 index 0000000000..613c9c2163 --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/Menu/Menu.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/net/haproxy/src/opnsense/mvc/app/views/OPNsense/HAProxy/index.volt b/net/haproxy/src/opnsense/mvc/app/views/OPNsense/HAProxy/index.volt new file mode 100644 index 0000000000..891095a5ef --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/views/OPNsense/HAProxy/index.volt @@ -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. + +#} + + + + + +
+ {% for tab in mainForm['tabs']|default([]) %} + {% if tab['subtabs']|default(false) %} + {# Tab with dropdown #} + {% for subtab in tab['subtabs']|default({})%} +
+ {{ partial("layout_partials/base_form",['fields':subtab[2],'id':'frm_'~subtab[0],'data_title':subtab[1],'apply_btn_id':'save_'~subtab[0]])}} +
+ {% endfor %} + {% endif %} + {% if tab['subtabs']|default(false)==false %} +
+ {{ partial("layout_partials/base_form",['fields':tab[2],'id':'frm_'~tab[0],'apply_btn_id':'save_'~tab[0]])}} +
+ {% endif %} + {% endfor %} + +
+ + + + + + + + + + + + + + + + + + + + +
{{ lang._('Enabled') }}{{ lang._('Frontend ID') }}{{ lang._('Frontend Name') }}{{ lang._('Description') }}{{ lang._('Commands') }}{{ lang._('ID') }}
+ + +
+ +
+
+ + +
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + +
{{ lang._('Enabled') }}{{ lang._('Backend ID') }}{{ lang._('Backend Name') }}{{ lang._('Description') }}{{ lang._('Commands') }}{{ lang._('ID') }}
+ + +
+ +
+
+ + +
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + +
{{ lang._('Server id') }}{{ lang._('Server Name') }}{{ lang._('Server Address') }}{{ lang._('Server Port') }}{{ lang._('Description') }}{{ lang._('Commands') }}{{ lang._('ID') }}
+ + +
+ +
+
+ + +
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
{{ lang._('Health Check ID') }}{{ lang._('Health Check Name') }}{{ lang._('Description') }}{{ lang._('Commands') }}{{ lang._('ID') }}
+ + +
+ +
+
+ + +
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
{{ lang._('Action ID') }}{{ lang._('Action Name') }}{{ lang._('Description') }}{{ lang._('Commands') }}{{ lang._('ID') }}
+ + +
+ +
+
+ + +
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
{{ lang._('ACL id') }}{{ lang._('ACL Name') }}{{ lang._('Description') }}{{ lang._('Commands') }}{{ lang._('ID') }}
+ + +
+ +
+
+ + +
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + +
{{ lang._('Enabled') }}{{ lang._('Lua ID') }}{{ lang._('Lua Script Name') }}{{ lang._('Description') }}{{ lang._('Commands') }}{{ lang._('ID') }}
+ + +
+ +
+
+ + +
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
{{ lang._('Error File ID') }}{{ lang._('Name') }}{{ lang._('Description') }}{{ lang._('Commands') }}{{ lang._('ID') }}
+ + +
+ +
+
+ + +
+
+
+
+
+ +{# 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'])}} diff --git a/net/haproxy/src/opnsense/mvc/app/views/OPNsense/HAProxy/statistics.volt b/net/haproxy/src/opnsense/mvc/app/views/OPNsense/HAProxy/statistics.volt new file mode 100644 index 0000000000..667747d71c --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/views/OPNsense/HAProxy/statistics.volt @@ -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. + +#} + + + + + +
+ +
+ + +
+
+
+ + + + +
+
+ +
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + +
{{ lang._('id') }}{{ lang._('Proxy') }}{{ lang._('Server') }}{{ lang._('Status') }}{{ lang._('Last Change') }}{{ lang._('Weight') }}{{ lang._('Active') }}{{ lang._('Downtime') }}
+
+
+ + + + +
+
+ +
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{ lang._('id') }}{{ lang._('Backend/Frontend') }}{{ lang._('Server') }}{{ lang._('Queue') }}{{ lang._('Max') }}{{ lang._('Limit') }}{{ lang._('Session Rate') }}{{ lang._('Max') }}{{ lang._('Limit') }}{{ lang._('Sessions') }}{{ lang._('Max') }}{{ lang._('Limit') }}{{ lang._('Total') }}{{ lang._('Bytes In') }}{{ lang._('Out') }}{{ lang._('Denied Req') }}{{ lang._('Resp') }}{{ lang._('Errors Req') }}{{ lang._('Conn') }}{{ lang._('Resp') }}{{ lang._('Warnings Retr') }}{{ lang._('Redis') }}
+
+
+ + + + +
+
+ +
+
+
+
+
+
+ +
+ + + + + + + + + + + + +
{{ lang._('Table') }}{{ lang._('Type') }}{{ lang._('Size') }}{{ lang._('Used') }}
+
+
+ + + + +
+
+ +
+
+
+
+
+
+ +
+ +{{ partial("layout_partials/base_dialog_processing") }} diff --git a/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/exportCerts.php b/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/exportCerts.php new file mode 100755 index 0000000000..e9aca1c45e --- /dev/null +++ b/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/exportCerts.php @@ -0,0 +1,66 @@ +#!/usr/local/bin/php +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"; + } + } + } + } + } +} diff --git a/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/exportErrorFiles.php b/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/exportErrorFiles.php new file mode 100755 index 0000000000..0241624515 --- /dev/null +++ b/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/exportErrorFiles.php @@ -0,0 +1,54 @@ +#!/usr/local/bin/php +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"; + } + } +} diff --git a/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/exportLuaScripts.php b/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/exportLuaScripts.php new file mode 100755 index 0000000000..599bb52125 --- /dev/null +++ b/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/exportLuaScripts.php @@ -0,0 +1,57 @@ +#!/usr/local/bin/php +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"; + } + } +} diff --git a/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/queryStats.php b/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/queryStats.php new file mode 100755 index 0000000000..08c780d512 --- /dev/null +++ b/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/queryStats.php @@ -0,0 +1,116 @@ +#!/usr/local/bin/php + 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"; +} diff --git a/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/setup.sh b/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/setup.sh new file mode 100755 index 0000000000..b8c57f0f0f --- /dev/null +++ b/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/setup.sh @@ -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 diff --git a/net/haproxy/src/opnsense/service/conf/actions.d/actions_haproxy.conf b/net/haproxy/src/opnsense/service/conf/actions.d/actions_haproxy.conf new file mode 100644 index 0000000000..a40d035d7e --- /dev/null +++ b/net/haproxy/src/opnsense/service/conf/actions.d/actions_haproxy.conf @@ -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 diff --git a/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/+MANIFEST b/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/+MANIFEST new file mode 100644 index 0000000000..5e864b8e60 --- /dev/null +++ b/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/+MANIFEST @@ -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: / diff --git a/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/+TARGETS b/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/+TARGETS new file mode 100644 index 0000000000..6e4c913d20 --- /dev/null +++ b/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/+TARGETS @@ -0,0 +1,2 @@ +haproxy.conf:/usr/local/etc/haproxy.conf +rc.conf.d:/etc/rc.conf.d/haproxy diff --git a/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf b/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf new file mode 100644 index 0000000000..8716b2c0bb --- /dev/null +++ b/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf @@ -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 %} + + diff --git a/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/rc.conf.d b/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/rc.conf.d new file mode 100644 index 0000000000..48bb5a3786 --- /dev/null +++ b/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/rc.conf.d @@ -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=""