Copyright (C) 2008 Remco Hoef Copyright (C) 2013 PiBa-NL merging (some of the) "haproxy-devel" changes from: Marcello Coutinho Copyright (C) 2013-2015 PiBa-NL 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, 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. */ $shortcut_section = "haproxy"; require("guiconfig.inc"); require_once("haproxy.inc"); require_once("haproxy_utils.inc"); require_once("haproxy_htmllist.inc"); require_once("pkg_haproxy_tabs.inc"); /* Compatibility function for pfSense 2.0 */ if (!function_exists("cert_get_purpose")) { function cert_get_purpose(){ $result = array(); $result['server'] = "Yes"; return $result; } } /**/ if (!is_array($config['installedpackages']['haproxy']['ha_backends']['item'])) { $config['installedpackages']['haproxy']['ha_backends']['item'] = array(); } $a_backend = &$config['installedpackages']['haproxy']['ha_backends']['item']; $a_pools = &$config['installedpackages']['haproxy']['ha_pools']['item']; if (!is_array($a_pools)) $a_pools = array(); uasort($a_pools, haproxy_compareByName); global $simplefields; $simplefields = array('name','desc','status','secondary','primary_frontend','type','forwardfor','httpclose','extaddr','backend_serverpool', 'max_connections','client_timeout','port','advanced_bind', 'ssloffloadcert','dcertadv','ssloffload','ssloffloadacl','ssloffloadacl_an','ssloffloadacladditional','ssloffloadacladditional_an', 'sslclientcert-none','sslclientcert-invalid','sslocsp', 'socket-stats', 'dontlognull','dontlog-normal','log-separate-errors','log-detailed'); if (isset($_POST['id'])) $id = $_POST['id']; else $id = $_GET['id']; if (isset($_GET['dup'])) $id = $_GET['dup']; $id = get_frontend_id($id); if (!is_numeric($id)) { //default value for new items. $pconfig['ssloffloadacl_an'] = "yes"; $new_item = array(); $new_item['extaddr'] = "wan_ipv4"; $new_item['extaddr_port'] = "80"; $pconfig['a_extaddr'][] = $new_item; } $servercerts = haproxy_get_certificates('server,user'); $fields_sslCertificates=array(); $fields_sslCertificates[0]['name']="ssl_certificate"; $fields_sslCertificates[0]['columnheader']="Certificates"; $fields_sslCertificates[0]['colwidth']="95%"; $fields_sslCertificates[0]['type']="select"; $fields_sslCertificates[0]['size']="500px"; $fields_sslCertificates[0]['items']=&$servercerts; $certs_ca = haproxy_get_certificates('ca'); $fields_caCertificates=array(); $fields_caCertificates[0]['name']="cert_ca"; $fields_caCertificates[0]['columnheader']="Certificates authorities"; $fields_caCertificates[0]['colwidth']="95%"; $fields_caCertificates[0]['type']="select"; $fields_caCertificates[0]['size']="500px"; $fields_caCertificates[0]['items']=&$certs_ca; $certs_crl = haproxy_get_crls(); //$ca_none['']['name']="None"; //$certs_crl = $ca_none + $certs_crl; $fields_crlCertificates=array(); $fields_crlCertificates[0]['name']="cert_crl"; $fields_crlCertificates[0]['columnheader']="Certificate revocation lists"; $fields_crlCertificates[0]['colwidth']="95%"; $fields_crlCertificates[0]['type']="select"; $fields_crlCertificates[0]['size']="500px"; $fields_crlCertificates[0]['items']=&$certs_crl; $fields_aclSelectionList=array(); $fields_aclSelectionList[0]['name']="name"; $fields_aclSelectionList[0]['columnheader']="Name"; $fields_aclSelectionList[0]['colwidth']="30%"; $fields_aclSelectionList[0]['type']="textbox"; $fields_aclSelectionList[0]['size']="20"; $fields_aclSelectionList[1]['name']="expression"; $fields_aclSelectionList[1]['columnheader']="Expression"; $fields_aclSelectionList[1]['colwidth']="30%"; $fields_aclSelectionList[1]['type']="select"; $fields_aclSelectionList[1]['size']="10"; $fields_aclSelectionList[1]['items']=&$a_acltypes; $fields_aclSelectionList[2]['name']="not"; $fields_aclSelectionList[2]['columnheader']="Not"; $fields_aclSelectionList[2]['colwidth']="5%"; $fields_aclSelectionList[2]['type']="checkbox"; $fields_aclSelectionList[2]['size']="5"; $fields_aclSelectionList[3]['name']="value"; $fields_aclSelectionList[3]['columnheader']="Value"; $fields_aclSelectionList[3]['colwidth']="35%"; $fields_aclSelectionList[3]['type']="textbox"; $fields_aclSelectionList[3]['size']="35"; $interfaces = haproxy_get_bindable_interfaces(); $interfaces_custom['custom']['name']="Use custom address:"; $interfaces = $interfaces_custom + $interfaces; $fields_externalAddress=array(); $fields_externalAddress[0]['name']="extaddr"; $fields_externalAddress[0]['columnheader']="Listen address"; $fields_externalAddress[0]['colwidth']="25%"; $fields_externalAddress[0]['type']="select"; $fields_externalAddress[0]['size']="200px"; $fields_externalAddress[0]['items']=&$interfaces; $fields_externalAddress[1]['name']="extaddr_custom"; $fields_externalAddress[1]['columnheader']="Custom address"; $fields_externalAddress[1]['colwidth']="25%"; $fields_externalAddress[1]['type']="textbox"; $fields_externalAddress[1]['size']="30"; $fields_externalAddress[2]['name']="extaddr_port"; $fields_externalAddress[2]['columnheader']="Port"; $fields_externalAddress[2]['colwidth']="5%"; $fields_externalAddress[2]['type']="textbox"; $fields_externalAddress[2]['size']="5"; $fields_externalAddress[3]['name']="extaddr_ssl"; $fields_externalAddress[3]['columnheader']="SSL Offloading"; $fields_externalAddress[3]['colwidth']="10%"; $fields_externalAddress[3]['type']="checkbox"; $fields_externalAddress[3]['size']="50px"; $fields_externalAddress[4]['name']="extaddr_advanced"; $fields_externalAddress[4]['columnheader']="Advanced"; $fields_externalAddress[4]['colwidth']="20%"; $fields_externalAddress[4]['type']="textbox"; $fields_externalAddress[4]['size']="30"; $fields_actions=array(); $fields_actions[0]['name']="action"; $fields_actions[0]['columnheader']="Action"; $fields_actions[0]['colwidth']="30%"; $fields_actions[0]['type']="select"; $fields_actions[0]['size']="200px"; $fields_actions[0]['items']=&$a_action; $fields_actions[1]['name']="parameters"; $fields_actions[1]['columnheader']="Parameters"; $fields_actions[1]['colwidth']="30%"; $fields_actions[1]['type']="fixedtext"; $fields_actions[1]['size']="200px"; $fields_actions[1]['text']="See below"; $fields_actions[2]['name']="acl"; $fields_actions[2]['columnheader']="Condition acl names"; $fields_actions[2]['colwidth']="15%"; $fields_actions[2]['type']="textbox"; $fields_actions[2]['size']="40"; $a_files = haproxy_get_fileslist(); $fields_errorfile = array(); $fields_errorfile[0]['name']="errorcode"; $fields_errorfile[0]['columnheader']="errorcode(s)"; $fields_errorfile[0]['colwidth']="15%"; $fields_errorfile[0]['type']="textbox"; $fields_errorfile[0]['size']="70px"; $fields_errorfile[1]['name']="errorfile"; $fields_errorfile[1]['columnheader']="Error Page"; $fields_errorfile[1]['colwidth']="30%"; $fields_errorfile[1]['type']="select"; $fields_errorfile[1]['size']="170px"; $fields_errorfile[1]['items']=&$a_files; $backends = get_haproxy_backends(); $a_action['use_backend']['fields']['backend']['items'] = &$backends; //$a_action['http-request_lua']['fields']['lua-script']['items'] = &$a_files; //$a_action['tcp-request_content_lua']['fields']['lua-script']['items'] = &$a_files; $fields_actions_details=array(); foreach($a_action as $key => $action) { if (is_array($action['fields'])) { foreach($action['fields'] as $field) { $item = $field; $name = $key . $item['name']; $item['name'] = $name; $item['columnheader'] = $field['name']; $item['customdrawcell'] = customdrawcell_actions; $fields_actions_details[$name] = $item; } } } $a_acltypes["backendservercount"]['fields']['backend']['items'] = &$backends; $fields_acl_details=array(); foreach($a_acltypes as $key => $action) { if (is_array($action['fields'])) { foreach($action['fields'] as $field) { $item = $field; $name = $key . $item['name']; $item['name'] = $name; $item['columnheader'] = $field['name']; $item['customdrawcell'] = customdrawcell_actions; $fields_acl_details[$name] = $item; } } } function customdrawcell_actions($object, $item, $itemvalue, $editable, $itemname, $counter) { if ($editable) { $object->haproxy_htmllist_drawcell($item, $itemvalue, $editable, $itemname, $counter); } else { //TODO hide fields not applicable.?. echo $itemvalue; } } $htmllist_extaddr = new HaproxyHtmlList("table_extaddr", $fields_externalAddress); $htmllist_extaddr->editmode = true; $htmllist_acls = new HaproxyHtmlList("table_acls", $fields_aclSelectionList); $htmllist_acls->fields_details = $fields_acl_details; //$htmllist_acls->editmode = true; $htmllist_actions = new HaproxyHtmlList("table_actions", $fields_actions); $htmllist_actions->fields_details = $fields_actions_details; //$htmllist_actions->keyfield = "name"; //$htmllist_actions->editmode = true; $htmllist_sslCertificates = new HaproxyHtmlList("tbl_sslCerts", $fields_sslCertificates); $htmllist_caCertificates = new HaproxyHtmlList("tbl_caCerts", $fields_caCertificates ); $htmllist_crlCertificates = new HaproxyHtmlList("tbl_crlCerts", $fields_crlCertificates); $errorfileslist = new HaproxyHtmlList("table_errorfile", $fields_errorfile); $errorfileslist->keyfield = "errorcode"; if (isset($id) && $a_backend[$id]) { $pconfig['a_acl']=&$a_backend[$id]['ha_acls']['item']; $pconfig['a_certificates']=&$a_backend[$id]['ha_certificates']['item']; $pconfig['clientcert_ca']=&$a_backend[$id]['clientcert_ca']['item']; $pconfig['clientcert_crl']=&$a_backend[$id]['clientcert_crl']['item']; $pconfig['a_extaddr']=&$a_backend[$id]['a_extaddr']['item']; $pconfig['a_actionitems']=&$a_backend[$id]['a_actionitems']['item']; $pconfig['a_errorfiles']=&$a_backend[$id]['a_errorfiles']['item']; $pconfig['advanced'] = base64_decode($a_backend[$id]['advanced']); foreach($simplefields as $stat) $pconfig[$stat] = $a_backend[$id][$stat]; } if (isset($_GET['dup'])) { unset($id); $pconfig['name'] .= "-copy"; if ($pconfig['secondary'] != 'yes') $pconfig['primary_frontend'] = $pconfig['name']; } $changedesc = "Services: HAProxy: Frontend"; $changecount = 0; if ($_POST) { $changecount++; unset($input_errors); $pconfig = $_POST; if ($pconfig['secondary'] != "yes") { $reqdfields = explode(" ", "name type"); $reqdfieldsn = explode(",", "Name,Type"); } else { $reqdfields = explode(" ", "name"); $reqdfieldsn = explode(",", "Name"); } $pf_version=substr(trim(file_get_contents("/etc/version")),0,3); if ($pf_version < 2.1) $input_errors = eval('do_input_validation($_POST, $reqdfields, $reqdfieldsn, &$input_errors); return $input_errors;'); else do_input_validation($_POST, $reqdfields, $reqdfieldsn, $input_errors); if (preg_match("/[^a-zA-Z0-9\.\-_]/", $_POST['name'])) $input_errors[] = "The field 'Name' contains invalid characters."; if ($pconfig['secondary'] != "yes") { if ($_POST['max_connections'] && !is_numeric($_POST['max_connections'])) $input_errors[] = "The field 'Max connections' value is not a number."; $ports = split(",", $_POST['port'] . ","); foreach($ports as $port) if ($port && !is_numeric($port) && !is_portoralias($port)) $input_errors[] = "The field 'Port' value '".htmlspecialchars($port)."' is not a number or alias thereof."; if ($_POST['client_timeout'] !== "" && !is_numeric($_POST['client_timeout'])) $input_errors[] = "The field 'Client timeout' value is not a number."; } /* Ensure that our pool names are unique */ for ($i=0; isset($config['installedpackages']['haproxy']['ha_backends']['item'][$i]); $i++) if (($_POST['name'] == $config['installedpackages']['haproxy']['ha_backends']['item'][$i]['name']) && ($i != $id)) $input_errors[] = "This frontend name has already been used. Frontend names must be unique. $i != $id"; $a_actionitems = $htmllist_actions->haproxy_htmllist_get_values(); $pconfig['a_actionitems'] = $a_actionitems; $a_errorfiles = $errorfileslist->haproxy_htmllist_get_values(); $pconfig['a_errorfiles'] = $a_errorfiles; $a_certificates = $htmllist_sslCertificates->haproxy_htmllist_get_values(); $pconfig['a_certificates'] = $a_certificates; $a_clientcert_ca = $htmllist_caCertificates->haproxy_htmllist_get_values(); $pconfig['clientcert_ca'] = $a_clientcert_ca; $a_clientcert_crl = $htmllist_crlCertificates->haproxy_htmllist_get_values(); $pconfig['clientcert_crl'] = $a_clientcert_crl; $a_acl = $htmllist_acls->haproxy_htmllist_get_values(); $pconfig['a_acl'] = $a_acl; $a_extaddr = $htmllist_extaddr->haproxy_htmllist_get_values(); $pconfig['a_extaddr'] = $a_extaddr; foreach($a_acl as $acl) { $acl_name = $acl['name']; $acl_value = $acl['value']; $acltype = haproxy_find_acl($acl['expression']); if (preg_match("/[^a-zA-Z0-9\.\-_]/", $acl_name)) $input_errors[] = "The field 'Name' contains invalid characters."; if (!isset($acltype['novalue'])) if (!preg_match("/.{1,}/", $acl_value)) $input_errors[] = "The field 'Value' is required."; if (!preg_match("/.{2,}/", $acl_name)) $input_errors[] = "The field 'Name' is required with at least 2 characters."; } foreach($a_extaddr as $extaddr) { $ports = explode(",",$extaddr['extaddr_port']); foreach($ports as $port){ if ($port && !is_numeric($port) && !is_portoralias($port)) $input_errors[] = "The field 'Port' value '".htmlspecialchars($port)."' is not a number or alias thereof."; } if ($extaddr['extaddr'] == 'custom') { $extaddr_custom = $extaddr['extaddr_custom']; if (empty($extaddr_custom) || (!is_ipaddroralias($extaddr_custom))) $input_errors[] = sprintf(gettext("%s is not a valid source IP address or alias."),$extaddr_custom); } } if (!$input_errors) { $backend = array(); if(isset($id) && $a_backend[$id]) $backend = $a_backend[$id]; if($backend['name'] != "") $changedesc .= " modified '{$backend['name']}' pool:"; // update references to this primary frontend if ($backend['name'] != $_POST['name']) { foreach($a_backend as &$frontend) { if ($frontend['primary_frontend'] == $backend['name']) { $frontend['primary_frontend'] = $_POST['name']; } } } foreach($simplefields as $stat) update_if_changed($stat, $backend[$stat], $_POST[$stat]); update_if_changed("advanced", $backend['advanced'], base64_encode($_POST['advanced'])); $backend['ha_acls']['item'] = $a_acl; $backend['ha_certificates']['item'] = $a_certificates; $backend['clientcert_ca']['item'] = $a_clientcert_ca; $backend['clientcert_crl']['item'] = $a_clientcert_crl; $backend['a_extaddr']['item'] = $a_extaddr; $backend['a_actionitems']['item'] = $a_actionitems; $backend['a_errorfiles']['item'] = $a_errorfiles; if (isset($id) && $a_backend[$id]) { $a_backend[$id] = $backend; } else { $a_backend[] = $backend; } if ($changecount > 0) { touch($d_haproxyconfdirty_path); write_config($changedesc); } header("Location: haproxy_listeners.php"); exit; } } $closehead = false; $pgtitle = "HAProxy: Frontend: Edit"; include("head.inc"); haproxy_css(); if (!isset($_GET['dup'])) $excludefrontend = $pconfig['name']; $primaryfrontends = get_haproxy_frontends($excludefrontend); ?>
Edit haproxy listener
Name size="25" maxlength="25" />
Description size="64" />
Status
Shared Frontend At least 1 primary frontend is needed.

onclick="updatevisibility();" /> This can be used to host a second or more website on the same IP:Port combination.
Use this setting to configure multiple backends/accesslists for a single frontend.
All settings of which only 1 can exist will be hidden.
The frontend settings will be merged into 1 set of frontend configuration.
Primary frontend
External address Draw($a_extaddr); ?>
If you want this rule to apply to another IP address than the IP address of the interface chosen above, select it here (you need to define Virtual IP addresses on the first). Also note that if you are trying to redirect connections on the LAN select the "any" option. In the port to listen to, if you want to specify multiple ports, separate them with a comma (,). EXAMPLE: 80,8000 Or to listen on both 80 and 443 create 2 rows in the table.
Max connections size="10" maxlength="10" />
Type
This defines the processing type of HAProxy, and will determine the availabe options for acl checks and also several other options.
Please note that for https encryption/decryption on HAProxy with a certificate the processing type needs to be set to 'http'.
Access Control lists Draw($a_acl); ?>
Example:
Name Expression Not Value
Backend1acl Host matches www.yourdomain.tld
addHeaderAcl SSL Client certificate valid

acl's with the same name will be 'combined' using OR criteria.
For more information about ACL's please see HAProxy Documentation Section 7 - Using ACL's

NOTE Important change in behaviour, since package version 0.32
-acl's are no longer combined with logical AND operators, list multiple acl's below where needed.
-acl's alone no longer implicitly generate use_backend configuration. Add 'actions' below to accomplish this behaviour.
Actions Draw($a_actionitems); ?>
Example:
Action Parameters Condition
Use Backend Website1Backend Backend1acl
http-request header set Headername: X-HEADER-ClientCertValid
New logformat value: YES
addHeaderAcl
Default Backend
 
Stats options
Separate sockets onclick='updatevisibility();' /> Enable collecting & providing separate statistics for each socket.
 
Logging options
Dont log null onclick='updatevisibility();' /> A connection on which no data has been transferred will not be logged.
To skip logging probes from monitoring systems that otherwise would pollute the logging. (It is generally recommended not to use this option in uncontrolled environments (eg: internet), otherwise scans and other malicious activities would not be logged.)
Dont log normal onclick='updatevisibility();' /> Don't log connections in which no anomalies are found.
Setting this option ensures that normal connections, those which experience no error, no timeout, no retry nor redispatch, will not be logged.
Raise level for errors onclick='updatevisibility();' /> Change the level changes from "info" to "err" for potentially interesting information.
This option makes haproxy raise the level of logs containing potentially interesting information such as errors, timeouts, retries, redispatches, or HTTP status codes 5xx.
Detailed logging onclick='updatevisibility();' /> If checked provides more detailed logging.
Each log line turns into a much richer format including, but not limited to, the connection timers, the session status, the connections numbers, the frontend, backend and server name, and of course the source address and ports. In http mode also the HTTP request and captured headers and cookies will be logged.
 
Error files
Use these to replace the error pages that haproxy can generate by custom pages created on the files tab. For example haproxy will generate a 503 error page when no backend is available, you can replace that page here.

Draw($a_errorfiles); ?>
 

 
Advanced settings
Client timeout size="10" maxlength="10" />
the time (in milliseconds) we accept to wait for data from the client, or for the client to accept data (default 30000).
Use 'forwardfor' option />
The 'forwardfor' option creates an HTTP 'X-Forwarded-For' header which contains the client's IP address. This is useful to let the final web server know what the client address was. (eg for statistics on domains)
Use 'httpclose' option
Bind pass thru size="64" />
NOTE: paste text into this box that you would like to pass behind the bind option.
Advanced pass thru
NOTE: paste text into this box that you would like to pass thru.
 
SSL Offloading
SSL Offloading will reduce web servers load by maintaining and encrypting connection with users on internet while sending and retrieving data without encrytion to internal servers. Also more ACL rules and http logging may be configured when this option is used. Certificates can be imported into the pfSense "Certificate Authority Manager" Please be aware this possibly will not work with all web applications. Some applications will require setting the SSL checkbox on the backend server configurations so the connection to the webserver will also be a encrypted connection, in that case there will be a slight overall performance loss.
Use Offloading onclick="updatevisibility();" />Specify additional certificates for this shared-frontend.
Certificate No Certificates defined.
Create one under System > Cert Manager.'); ?>
Choose the cert to use on this frontend.
onclick="updatevisibility();" />Add ACL for certificate CommonName. (host header matches the 'CN' of the certificate)
onclick="updatevisibility();" />Add ACL for certificate Subject Alternative Names.
OCSP onclick="updatevisibility();" />Load certificate ocsp responses for easy certificate validation by the client.
Additional certificates Which of these certificate will be send will be determined by haproxys SNI recognition. If the browser does not send SNI this will not work properly. (IE on XP is one example, possibly also older browsers or mobile devices) Draw($a_certificates); ?>
onclick="updatevisibility();" />Add ACL for certificate CommonName. (host header matches the 'CN' of the certificate)
onclick="updatevisibility();" />Add ACL for certificate Subject Alternative Names.
Advanced ssl options />
NOTE: Paste additional ssl options(without commas) to include on ssl listening options.
some options: force-sslv3, force-tlsv10 force-tlsv11 force-tlsv12 no-sslv3 no-tlsv10 no-tlsv11 no-tlsv12 no-tls-tickets
Example: no-sslv3 ciphers EECDH+aRSA+AES:TLSv1+kRSA+AES:TLSv1+kRSA+3DES
Client certificate verification options, leave all these options empty if you do not want to ask for a client certificate
The users that visit this site will need to load the client cert signed by one of the ca's listed below imported into their browser.
Without client cert onclick='updatevisibility();' /> Allows clients without a certificate to connect.
Make sure to add appropriate acl's to check for presence of a user certificate where needed.
Allow invalid cert onclick='updatevisibility();' /> Allows client with a invalid/expired/revoked or otherwise wrong certificate to connect.
Make sure to add appropriate acl's to check for valid certificates and verify errors using codes from the following list. https://www.openssl.org/docs/apps/verify.html#DIAGNOSTICS
Client verification CA certificates Client certificate will be verified against these CA certificates. Draw($a_certificates); ?>
Client verification CRL Client certificate will be verified against these CRL revocation lists. Draw($a_certificates); ?>
 
 
NOTE: You must add a firewall rule permitting access to this frontend!