From f052b1e4fcec837f819fe05dbd38a0ed87e39333 Mon Sep 17 00:00:00 2001 From: PiBa-NL Date: Sun, 14 Sep 2014 16:47:39 +0200 Subject: haproxy-devel improvements -server certificate check options -client-certificate support -logging options -unix sockets for faster backend>frontend communication --- config/haproxy-devel/haproxy.inc | 472 ++++++++++++++++++------ config/haproxy-devel/haproxy_global.php | 122 +++--- config/haproxy-devel/haproxy_htmllist.inc | 168 +++++++-- config/haproxy-devel/haproxy_listeners.php | 24 +- config/haproxy-devel/haproxy_listeners_edit.php | 128 ++++++- config/haproxy-devel/haproxy_pool_edit.php | 177 +++++++-- config/haproxy-devel/haproxy_utils.inc | 110 +++--- 7 files changed, 896 insertions(+), 305 deletions(-) diff --git a/config/haproxy-devel/haproxy.inc b/config/haproxy-devel/haproxy.inc index 1e403c48..ce367fb8 100644 --- a/config/haproxy-devel/haproxy.inc +++ b/config/haproxy-devel/haproxy.inc @@ -39,33 +39,50 @@ $d_haproxyconfdirty_path = $g['varrun_path'] . "/haproxy.conf.dirty"; global $a_acltypes; $a_acltypes = array(); -$a_acltypes["host_starts_with"] = array('name' => 'Host starts with', +$a_acltypes["host_starts_with"] = array('name' => 'Host starts with:', 'mode' => 'http', 'syntax' => 'hdr_beg(host) -i %1$s'); -$a_acltypes["host_ends_with"] = array('name' => 'Host ends with', +$a_acltypes["host_ends_with"] = array('name' => 'Host ends with:', 'mode' =>'http', 'syntax' => 'hdr_end(host) -i %1$s'); -$a_acltypes["host_matches"] = array('name' => 'Host matches', +$a_acltypes["host_matches"] = array('name' => 'Host matches:', 'mode' =>'http', 'syntax' => 'hdr(host) -i %1$s'); -$a_acltypes["host_regex"] = array('name' => 'Host regex', +$a_acltypes["host_regex"] = array('name' => 'Host regex:', 'mode' =>'http', 'syntax' => 'hdr_reg(host) -i %1$s'); -$a_acltypes["host_contains"] = array('name' => 'Host contains', +$a_acltypes["host_contains"] = array('name' => 'Host contains:', 'mode' => 'http', 'syntax' => 'hdr_dir(host) -i %1$s'); -$a_acltypes["path_starts_with"] = array('name' => 'Path starts with', +$a_acltypes["path_starts_with"] = array('name' => 'Path starts with:', 'mode' => 'http', 'syntax' => 'path_beg -i %1$s'); -$a_acltypes["path_ends_with"] = array('name' => 'Path ends with', +$a_acltypes["path_ends_with"] = array('name' => 'Path ends with:', 'mode' => 'http', 'syntax' => 'path_end -i %1$s'); -$a_acltypes["path_matches"] = array('name' => 'Path matches', +$a_acltypes["path_matches"] = array('name' => 'Path matches:', 'mode' => 'http', 'syntax' => 'path -i %1$s'); -$a_acltypes["path_regex"] = array('name' => 'Path regex', +$a_acltypes["path_regex"] = array('name' => 'Path regex:', 'mode' => 'http', 'syntax' => 'path_reg -i %1$s'); -$a_acltypes["path_contains"] = array('name' => 'Path contains', +$a_acltypes["path_contains"] = array('name' => 'Path contains:', 'mode' => 'http', 'syntax' => 'path_dir -i %1$s'); -$a_acltypes["source_ip"] = array('name' => 'Source IP', +$a_acltypes["ssl_c_verify_code"] = array('name' => 'SSL Client certificate verify error result:', + 'mode' => 'http', 'syntax' => 'ssl_fc_has_crt ssl_c_verify %1$s'); + // ssl_c_verify result codes: https://www.openssl.org/docs/apps/verify.html#DIAGNOSTICS +$a_acltypes["ssl_c_verify"] = array('name' => 'SSL Client certificate valid.', + 'mode' => 'http', 'syntax' => 'ssl_fc_has_crt ssl_c_verify 0 '); +$a_acltypes["ssl_c_ca_commonname"] = array('name' => 'SSL Client issued by CA common-name:', + 'mode' => 'http', 'syntax' => 'ssl_c_i_dn(CN) %1$s'); +$a_acltypes["source_ip"] = array('name' => 'Source IP:', 'mode' => '', 'syntax' => 'src %1$s'); -$a_acltypes["backendservercount"] = array('name' => 'Minimum count usable servers', +$a_acltypes["backendservercount"] = array('name' => 'Minimum count usable servers:', 'mode' => '', 'syntax' => 'nbsrv(%2$s) ge %1$d', 'parameters' => 'value,backendname'); +$a_acltypes["traffic_is_http"] = array('name' => 'Traffic is http (no value needed):', 'inspect-delay' => '5', + 'mode' => 'tcp', 'syntax' => 'req.proto_http', 'advancedoptions' => "tcp-request content accept if { req.proto_http }"); +$a_acltypes["traffic_is_ssl"] = array('name' => 'Traffic is ssl (no value needed):', 'inspect-delay' => '5', + 'mode' => 'tcp', 'syntax' => 'req.ssl_ver gt 0', 'advancedoptions' => "tcp-request content accept if { req.ssl_ver gt 0 }"); // 'ssl_sni_matches' was added in HAProxy1.5dev17 -$a_acltypes["ssl_sni_matches"] = array('name' => 'Server Name Indication TLS extension matches', - 'mode' => 'https', 'syntax' => 'req_ssl_sni -i %1$s', 'advancedoptions' => "tcp-request inspect-delay 5s\n\ttcp-request content accept if { req_ssl_hello_type 1 }"); +$a_acltypes["ssl_sni_matches"] = array('name' => 'Server Name Indication TLS extension matches:', 'inspect-delay' => '5', + 'mode' => 'https', 'syntax' => 'req.ssl_sni -i %1$s', 'advancedoptions' => "tcp-request content accept if { req.ssl_hello_type 1 }"); +$a_acltypes["ssl_sni_contains"] = array('name' => 'Server Name Indication TLS extension contains:', 'inspect-delay' => '5', + 'mode' => 'https', 'syntax' => 'req.ssl_sni -m sub -i example %1$s', 'advancedoptions' => "tcp-request content accept if { req.ssl_hello_type 1 }"); +$a_acltypes["ssl_sni_starts_with"] = array('name' => 'Server Name Indication TLS extension starts with:', 'inspect-delay' => '5', + 'mode' => 'https', 'syntax' => 'req.ssl_sni -m beg -i example %1$s', 'advancedoptions' => "tcp-request content accept if { req.ssl_hello_type 1 }"); +$a_acltypes["ssl_sni_ends_with"] = array('name' => 'Server Name Indication TLS extension ends with:', 'inspect-delay' => '5', + 'mode' => 'https', 'syntax' => 'req.ssl_sni -m end -i example %1$s', 'advancedoptions' => "tcp-request content accept if { req.ssl_hello_type 1 }"); global $a_checktypes; $a_checktypes = array(); @@ -106,16 +123,18 @@ $a_httpcheck_method['TRACE'] = array('name' => 'TRACE', 'syntax' => 'TRACE'); global $a_closetypes; $a_closetypes = array(); -$a_closetypes['none'] = array('name' => 'none', 'syntax' => '', - 'descr' => 'No close headers will be changed.'); +//$a_closetypes['none'] = array('name' => 'none', 'syntax' => '', +// 'descr' => 'No close headers will be changed.'); +$a_closetypes['http-keep-alive'] = array('name' => 'http-keep-alive (default)', 'syntax' => 'http-keep-alive', + 'descr' => 'By default HAProxy operates in keep-alive mode with regards to persistent connections: for each connection it processes each request and response, and leaves the connection idle on both sides between the end of a response and the start of a new request.'); +$a_closetypes['http-tunnel'] = array('name' => 'http-tunnel', 'syntax' => 'http-tunnel', + 'descr' => 'Option "http-tunnel" disables any HTTP processing past the first request and the first response. This is the mode which was used by default in versions 1.0 to 1.5-dev21. It is the mode with the lowest processing overhead, which is normally not needed anymore unless in very specific cases such as when using an in-house protocol that looks like HTTP but is not compatible, or just to log one request per client in order to reduce log size. Note that everything which works at the HTTP level, including header parsing/addition, cookie processing or content switching will only work for the first request and will be ignored after the first response.'); $a_closetypes['httpclose'] = array('name' => 'httpclose', 'syntax' => 'httpclose', 'descr' => 'The "httpclose" option removes any "Connection" header both ways, and adds a "Connection: close" header in each direction. This makes it easier to disable HTTP keep-alive than the previous 4-rules block.'); $a_closetypes['http-server-close'] = array('name' => 'http-server-close', 'syntax' => 'http-server-close', 'descr' => 'By default, when a client communicates with a server, HAProxy will only analyze, log, and process the first request of each connection. Setting "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. This provides the lowest latency on the client side (slow network) and the fastest session reuse on the server side to save server resources.'); $a_closetypes['forceclose'] = array('name' => 'forceclose', 'syntax' => 'forceclose', 'descr' => 'Some HTTP servers do not necessarily close the connections when they receive the "Connection: close" set by "option httpclose", and if the client does not close either, then the connection remains open till the timeout expires. This causes high number of simultaneous connections on the servers and shows high global session times in the logs. Note that this option also enables the parsing of the full request and response, which means we can close the connection to the server very quickly, releasing some resources earlier than with httpclose.'); -$a_closetypes['http-keep-alive'] = array('name' => 'http-keep-alive', 'syntax' => 'http-keep-alive', - 'descr' => 'By default, when a client communicates with a server, HAProxy will only analyze, log, and process the first request of each connection. Setting "option http-keep-alive" enables HTTP keep-alive mode on the client- and server- sides. This provides the lowest latency on the client side (slow network) and the fastest session reuse on the server side at the expense of maintaining idle connections to the servers. In general, it is possible with this option to achieve approximately twice the request rate that the "http-server-close" option achieves on small objects. There are mainly two situations where this option may be useful : - when the server is non-HTTP compliant and authenticates the connection instead of requests (eg: NTLM authentication) - when the cost of establishing the connection to the server is significant compared to the cost of retrieving the associated object from the server.'); global $a_servermodes; $a_servermodes = array(); @@ -338,7 +357,10 @@ EOD; $writeconfigupdate = false; /* Do XML upgrade from haproxy 0.31 to haproxy-dev */ if (is_array($config['installedpackages']['haproxy']['ha_servers'])) { - /* We have an old config */ + $static_output .= "HAProxy, Do XML upgrade from haproxy 0.31 to haproxy-dev\n"; + update_output_window($static_output); + + /* We have an old config */ $config['installedpackages']['haproxy']['ha_pools']['item'] = array(); $a_global = &$config['installedpackages']['haproxy']; $a_backends = &$config['installedpackages']['haproxy']['ha_backends']['item']; @@ -383,8 +405,10 @@ EOD; } /* XML update to: pkg v1.3 and 'pool' changed to 'backend_serverpool' because 'pool' was added to listtags() in xmlparse.inc */ - if (is_array($config['installedpackages']['haproxy']['ha_backends']['item'][0]['pool'])) - { + if (is_array($config['installedpackages']['haproxy']['ha_backends']['item'][0]['pool'])) { + $static_output .= "HAProxy, Do XML upgrade, change to backend_serverpool from pool array\n"; + update_output_window($static_output); + foreach($config['installedpackages']['haproxy']['ha_backends']['item'] as &$frontend) { $backend_serverpool = $frontend['pool'][0]; @@ -394,8 +418,9 @@ EOD; $writeconfigupdate = true; } //also move setting for existing 2.0 installations as only the new variable is used - if (isset($config['installedpackages']['haproxy']['ha_backends']['item'][0]['pool'])) - { + if (isset($config['installedpackages']['haproxy']['ha_backends']['item'][0]['pool'])) { + $static_output .= "HAProxy, Do XML upgrade, change to backend_serverpool from pool\n"; + update_output_window($static_output); foreach($config['installedpackages']['haproxy']['ha_backends']['item'] as &$frontend) { $backend_serverpool = $frontend['pool']; @@ -406,6 +431,8 @@ EOD; } // update config to "haproxy-devel 1.5-dev19 pkg v0.5" if(is_array($config['installedpackages']['haproxy']['ha_backends']['item'])) { + $static_output .= "HAProxy, Do XML upgrade, update frontend options\n"; + update_output_window($static_output); foreach ($config['installedpackages']['haproxy']['ha_backends']['item'] as &$bind) { if($bind['httpclose'] && $bind['httpclose'] == "yes" ) { $bind['httpclose'] = "httpclose"; @@ -498,7 +525,7 @@ function haproxy_find_acl($name) { } } -function write_backend($fd, $name, $pool, $frontend) { +function write_backend($configpath, $fd, $name, $pool, $frontend) { if(!is_array($pool['ha_servers']['item']) && !$pool['stats_enabled']=='yes') return; global $a_checktypes, $a_cookiemode; @@ -515,8 +542,11 @@ function write_backend($fd, $name, $pool, $frontend) { $backend_mode = $frontendtype; } fwrite ($fd, "\tmode\t\t\t" . $backend_mode . "\n"); - + if ($pool['log-health-checks'] == 'yes') + fwrite ($fd, "\toption\t\t\tlog-health-checks\n"); + if ($frontendtype == "http") { + // actions that read/write http headers only work when 'mode http' is used if ($pool["persist_cookie_enabled"] == "yes") { $cookie_mode = $pool["persist_cookie_mode"]; $cookie_cachable = $pool["persist_cookie_cachable"]; @@ -525,14 +555,52 @@ function write_backend($fd, $name, $pool, $frontend) { $cookie .= $cookie_cachable == "yes" ? "" : " nocache"; fwrite ($fd, "\t" . $cookie . "\n"); } - } + + if ($pool["strict_transport_security"] && is_numeric($pool["strict_transport_security"])){ + fwrite ($fd, "\trspadd Strict-Transport-Security:\\ max-age={$pool["strict_transport_security"]};\n"); + } + + if ($pool["cookie_attribute_secure"] == 'yes'){ + fwrite ($fd, "\trspirep ^(Set-Cookie:((?!;\\ secure).)*)$ \\1;\ secure if { ssl_fc }\n"); + } + + if($pool['stats_enabled']=='yes') { + fwrite ($fd, "\tstats\t\t\tenable\n"); + if($pool['stats_uri']) + fwrite ($fd, "\tstats\t\t\turi ".$pool['stats_uri']."\n"); + if($pool['stats_realm']) + fwrite ($fd, "\tstats\t\t\trealm " . haproxy_escapestring($pool['stats_realm']) . "\n"); + else + fwrite ($fd, "\tstats\t\t\trealm .\n"); + + if ($pool['stats_username'] && $pool['stats_password']) + fwrite ($fd, "\tstats\t\t\tauth " . haproxy_escapestring($pool['stats_username']).":". haproxy_escapestring($pool['stats_password'])."\n"); + + if($pool['stats_admin']=='yes') + fwrite ($fd, "\tstats\t\t\tadmin if TRUE" . "\n"); + + if($pool['stats_node']) + fwrite ($fd, "\tstats\t\t\tshow-node " . $pool['stats_node'] . "\n"); + if($pool['stats_desc']) + fwrite ($fd, "\tstats\t\t\tshow-desc " . haproxy_escapestring($pool['stats_desc']) . "\n"); + if($pool['stats_refresh']) + fwrite ($fd, "\tstats\t\t\trefresh " . $pool['stats_refresh'] . "\n"); + + if ($pool['stats_scope']) { + $scope_items = explode(",", $pool['stats_scope']); + foreach($scope_items as $scope_item) + fwrite ($fd, "\tstats\t\t\tscope " . $scope_item . "\n"); + } + } + } + switch($pool["persist_sticky_type"]) { case 'stick_sslsessionid': if ($frontendtype == "https") { fwrite ($fd, "\ttcp-request inspect-delay 5s\n"); fwrite ($fd, "\tstick-table type binary len 32 size ".$pool["persist_stick_tablesize"]." expire ".$pool["persist_stick_expire"]."\n"); - fwrite ($fd, "\tacl clienthello req_ssl_hello_type 1\n"); - fwrite ($fd, "\tacl serverhello rep_ssl_hello_type 2\n"); + fwrite ($fd, "\tacl clienthello req.ssl_hello_type 1\n"); + fwrite ($fd, "\tacl serverhello res.ssl_hello_type 2\n"); fwrite ($fd, "\ttcp-request content accept if clienthello\n"); fwrite ($fd, "\ttcp-response content accept if serverhello\n"); fwrite ($fd, "\tstick on payload_lv(43,1) if clienthello\n"); @@ -564,8 +632,7 @@ function write_backend($fd, $name, $pool, $frontend) { unset($checkport); $check_type = $pool['check_type']; - if ($check_type != 'none') - { + if ($check_type != 'none') { $optioncheck = $a_checktypes[$check_type]['syntax']; if ($check_type == "MySQL" || $check_type == "PostgreSQL") $optioncheck .= " user " . $pool['monitor_username']; @@ -601,35 +668,6 @@ function write_backend($fd, $name, $pool, $frontend) { if ($pool['transparent_clientip']) fwrite ($fd, "\tsource 0.0.0.0 usesrc clientip\n"); - - if($pool['stats_enabled']=='yes') { - fwrite ($fd, "\tstats\t\t\tenable\n"); - if($pool['stats_uri']) - fwrite ($fd, "\tstats\t\t\turi ".$pool['stats_uri']."\n"); - if($pool['stats_realm']) - fwrite ($fd, "\tstats\t\t\trealm " . haproxy_escapestring($pool['stats_realm']) . "\n"); - else - fwrite ($fd, "\tstats\t\t\trealm .\n"); - - if ($pool['stats_username'] && $pool['stats_password']) - fwrite ($fd, "\tstats\t\t\tauth " . haproxy_escapestring($pool['stats_username']).":". haproxy_escapestring($pool['stats_password'])."\n"); - - if($pool['stats_admin']=='yes') - fwrite ($fd, "\tstats\t\t\tadmin if TRUE" . "\n"); - - if($pool['stats_node']) - fwrite ($fd, "\tstats\t\t\tshow-node " . $pool['stats_node'] . "\n"); - if($pool['stats_desc']) - fwrite ($fd, "\tstats\t\t\tshow-desc " . haproxy_escapestring($pool['stats_desc']) . "\n"); - if($pool['stats_refresh']) - fwrite ($fd, "\tstats\t\t\trefresh " . $pool['stats_refresh'] . "\n"); - - if ($pool['stats_scope']) { - $scope_items = explode(",", $pool['stats_scope']); - foreach($scope_items as $scope_item) - fwrite ($fd, "\tstats\t\t\tscope " . $scope_item . "\n"); - } - } $uri = $pool['monitor_uri']; if ($pool['monitor_uri']) @@ -639,10 +677,6 @@ function write_backend($fd, $name, $pool, $frontend) { if ($optioncheck) fwrite ($fd, "\toption\t\t\t{$optioncheck}\n"); - - if ($pool["strict_transport_security"] && is_numeric($pool["strict_transport_security"])){ - fwrite ($fd, "\trspadd Strict-Transport-Security:\ max-age={$pool["strict_transport_security"]};\n"); - } if ($pool['advanced_backend']) { $adv_be = explode("\n", base64_decode($pool['advanced_backend'])); @@ -690,15 +724,57 @@ function write_backend($fd, $name, $pool, $frontend) { $isbackup = ""; } $ssl = ""; + $cafile = ""; + $crlfile = ""; + $crtfile = ""; + $verifynone = ""; + $verifyhost = ""; if ($be['ssl'] == 'yes') { $ssl = $frontendtype == "http" ? ' ssl' : ' check-ssl'; + + if ($be['sslserververify'] != 'yes') { + $verifynone = " verify none"; + } else { + $verifyhost = isset($be['verifyhost']) && $be['verifyhost'] != "" ? " verifyhost {$be['verifyhost']}" : ""; + + $ca = $be['ssl-server-ca']; + $filename = "$configpath/ca_$ca.pem"; + haproxy_write_certificate_crt($filename, $ca); + $cafile = " ca-file $filename"; + + $crl = $be['ssl-server-crl']; + if ($crl && $crl != "") { + $filename = "$configpath/crl_$crl.pem"; + haproxy_write_certificate_crl($filename, $crl); + $crlfile = " crl-file $filename"; + } + } + + $server_clientcert = $be['ssl-server-clientcert']; + if ($server_clientcert && $server_clientcert != "") { + $filename = "$configpath/server_clientcert_$server_clientcert.pem"; + haproxy_write_certificate_crt($filename, $server_clientcert, true); + $crtfile = " crt $filename"; + } + } $weight = ""; if (is_numeric($be['weight'])){ $weight = " weight " . $be['weight']; } - fwrite ($fd, "\tserver\t\t\t" . $be['name'] . " " . $be['address'].":" . $be['port'] . "$ssl$cookie$checkinter$checkport$agentcheck $isbackup$weight{$advanced_txt} {$be['advanced']}\n"); + $maxconn = ""; + if (is_numeric($be['maxconn'])){ + $maxconn = " maxconn " . $be['maxconn']; + } + + if ($be['forwardto'] && $be['forwardto'] != "") { + $server = "/{$be['forwardto']}.socket send-proxy-v2-ssl-cn"; + } else + $server = $be['address'].":" . $be['port']; + + + fwrite ($fd, "\tserver\t\t\t" . $be['name'] . " " . $server . "$ssl$cookie$checkinter$checkport$agentcheck $isbackup$weight$maxconn$cafile$crlfile$verifynone$verifyhost$crtfile{$advanced_txt} {$be['advanced']}\n"); } } fwrite ($fd, "\n"); @@ -735,24 +811,56 @@ function haproxy_check_and_run(&$messages, $reload) { } return $ok; } -function haproxy_write_certificate_file($filename, $certid) { - $cert = lookup_cert($certid); + +function haproxy_lookup_cert($certid) { + $res = lookup_ca($certid); + if (!$res) + $res = lookup_cert($certid); + return $res; +} + +function haproxy_write_certificate_crt($filename, $certid, $include_psk = false, $append = false) { + $cert = haproxy_lookup_cert($certid); + $certcontent = base64_decode($cert['crt']); + if ($include_psk && isset($cert['prv'])) + $certcontent .= "\r\n".base64_decode($cert['prv']); + $flags = $append ? FILE_APPEND : 0; + file_put_contents($filename, $certcontent, $flags); + unset($certcontent); + unset($cert); +} + +function haproxy_write_certificate_crl($filename, $crlid, $append = false) { + $crl = lookup_crl($crlid); + $content = base64_decode($crl['text']); + $flags = $append ? FILE_APPEND : 0; + file_put_contents($filename, $content, $flags); + unset($content); + unset($crl); +} + +function haproxy_write_certificate_fullchain($filename, $certid, $append = false) { + $cert = haproxy_lookup_cert($certid); $certcontent = base64_decode($cert['crt']); - $certcontent .= "\r\n".base64_decode($cert['prv']); + if (isset($cert['prv'])) + $certcontent .= "\r\n".base64_decode($cert['prv']); $certchaincontent = ca_chain($cert); if ($certchaincontent != "") { $certcontent .= "\r\n" . $certchaincontent; } unset($certchaincontent); - file_put_contents($filename, $certcontent); + $flags = $append ? FILE_APPEND : 0; + file_put_contents($filename, $certcontent, $flags); unset($certcontent); unset($cert); } function haproxy_writeconf($configpath) { global $config; + $chroot_dir = "/tmp/haproxy_chroot"; // can contain socket to forward connection from backend to frontend. "/var/empty" + make_dirs($chroot_dir); $configfile = $configpath . "/haproxy.cfg"; @@ -782,10 +890,15 @@ function haproxy_writeconf($configpath) { else $numprocs ="1"; fwrite ($fd, "\tnbproc\t\t\t$numprocs\n"); - fwrite ($fd, "\tchroot\t\t\t/var/empty\n"); + fwrite ($fd, "\tchroot\t\t\t$chroot_dir\n"); fwrite ($fd, "\tdaemon\n"); - fwrite ($fd, "\tssl-server-verify none\n"); + //fwrite ($fd, "\tssl-server-verify none\n"); + if($a_global['ssldefaultdhparam']) + fwrite ($fd, "\ttune.ssl.default-dh-param\t{$a_global['ssldefaultdhparam']}\n"); + if($a_global['log-send-hostname']) + fwrite ($fd, "\tlog-send-hostname\t\t{$a_global['log-send-hostname']}\n"); + // Keep the advanced options on the bottom of the global settings, to allow additional sections to be easely added if($a_global['advanced']) { $adv = explode("\n", base64_decode($a_global['advanced'])); @@ -823,45 +936,34 @@ function haproxy_writeconf($configpath) { continue; $primaryfrontend = get_primaryfrontend($frontend); $bname = get_frontend_ipport($frontend); + if (!is_array($a_bind[$bname])) { + $a_bind[$bname] = array(); + $a_bind[$bname] = $primaryfrontend; + $a_bind[$bname]['config'] = array(); + } //check ssl info if (strtolower($primaryfrontend['type']) == "http" && $frontend['ssloffload']){ //ssl crt ./server.pem ca-file ./ca.crt verify optional crt-ignore-err all crl-file ./ca_crl.pem $filename = "$configpath/{$frontend['name']}.{$frontend['port']}.pem"; $ssl_crt = " crt $filename"; - haproxy_write_certificate_file($filename, $frontend['ssloffloadcert']); + haproxy_write_certificate_fullchain($filename, $frontend['ssloffloadcert']); $subfolder = "$configpath/{$frontend['name']}.{$frontend['port']}"; $certs = $frontend['ha_certificates']['item']; if (is_array($certs)){ if (count($certs) > 0){ make_dirs($subfolder); foreach($certs as $cert){ - haproxy_write_certificate_file("$subfolder/{$cert['ssl_certificate']}.pem", $cert['ssl_certificate']); + haproxy_write_certificate_fullchain("$subfolder/{$cert['ssl_certificate']}.pem", $cert['ssl_certificate']); } $ssl_crt .= " crt $subfolder"; } } }else{ $ssl_crt=""; - unlink_if_exists("var/etc/{$frontend['name']}.{$frontend['port']}.crt"); + unlink_if_exists("var/etc/{$frontend['name']}.{$frontend['port']}.crt");//cleanup for possible old haproxy package version } - if (!is_array($a_bind[$bname])) { - $a_bind[$bname] = array(); - $a_bind[$bname]['config'] = array(); - // Settings which are used only from the primary frontend - $a_bind[$bname]['name'] = $primaryfrontend['name']; - $a_bind[$bname]['extaddr'] = $primaryfrontend['extaddr']; - $a_bind[$bname]['port'] = $primaryfrontend['port']; - $a_bind[$bname]['type'] = $primaryfrontend['type']; - $a_bind[$bname]['forwardfor'] = $primaryfrontend['forwardfor']; - $a_bind[$bname]['httpclose'] = $primaryfrontend['httpclose']; - $a_bind[$bname]['max_connections'] = $primaryfrontend['max_connections']; - $a_bind[$bname]['client_timeout'] = $primaryfrontend['client_timeout']; - $a_bind[$bname]['advanced'] = $primaryfrontend['advanced']; - $a_bind[$bname]['ssloffload'] = $primaryfrontend['ssloffload']; - $a_bind[$bname]['advanced_bind'] = $primaryfrontend['advanced_bind']; - } $b = &$a_bind[$bname]; if (($frontend['secondary'] != 'yes') && ($frontend['name'] != $b['name'])) { @@ -892,13 +994,34 @@ function haproxy_writeconf($configpath) { $advancedextra = array(); + $ca_file = ""; + $first = true; + if (is_array($bind['clientcert_ca']['item'])){ + foreach($bind['clientcert_ca']['item'] as $ca){ + $filename = "$configpath/clientca_{$bind['name']}.pem"; + haproxy_write_certificate_crt($filename, $ca['cert_ca'], false, !$first); + $first = false; + } + $ca_file = " ca-file $filename verify optional"; + } + $crl_file = ""; + $first = true; + if (is_array($bind['clientcert_crl']['item'])){ + foreach($bind['clientcert_crl']['item'] as $ca){ + $filename = "$configpath/clientcrl_{$bind['name']}.pem"; + haproxy_write_certificate_crl($filename, $ca['cert_crl'], !$first); + $first = false; + } + $crl_file = " crl-file $filename"; + } + // Prepare ports for processing by splitting $portss = "{$bind['port']},"; $ports = split(",", $portss); if($bind['type'] == "http") { // ssl offloading is only possible in http mode. - $ssl_info = $bind['ssl_info']; + $ssl_info = $bind['ssl_info'].$ca_file.$crl_file; $advanced_bind = $bind['advanced_bind']; } else { $ssl_info = ""; @@ -923,6 +1046,9 @@ function haproxy_writeconf($configpath) { fwrite ($fd, "{$frontendinfo}"); fwrite ($fd, "{$listenip}"); + if (use_frontend_as_unixsocket($bind['name'])){ + fwrite ($fd, "\tbind /tmp/haproxy_chroot/{$bind['name']}.socket accept-proxy {$ssl_info} {$advanced_bind}\n"); + } // Advanced pass thru if($bind['advanced']) { @@ -943,7 +1069,19 @@ function haproxy_writeconf($configpath) { fwrite ($fd, "\tmode\t\t\t" . $backend_type . "\n"); fwrite ($fd, "\tlog\t\t\tglobal\n"); - fwrite ($fd, "\toption\t\t\tdontlognull\n"); + + if ($bind['dontlognull'] == 'yes') + fwrite ($fd, "\toption\t\t\tdontlognull\n"); + if ($bind['dontlog-normal'] == 'yes') + fwrite ($fd, "\toption\t\t\tdontlog-normal\n"); + if ($bind['log-separate-errors'] == 'yes') + fwrite ($fd, "\toption\t\t\tlog-separate-errors\n"); + if ($bind['log-detailed'] == 'yes'){ + if ($backend_type == 'http') + fwrite ($fd, "\toption\t\t\thttplog\n"); + else + fwrite ($fd, "\toption\t\t\ttcplog\n"); + } if ($backend_type == 'http') { @@ -970,6 +1108,7 @@ function haproxy_writeconf($configpath) { // Combine the rest of the frontend configs $default_backend = ""; + $inspectdelay = 0; $i = 0; foreach ($bind['config'] as $frontend) { $a_acl = get_frontend_acls($frontend); @@ -1012,6 +1151,9 @@ function haproxy_writeconf($configpath) { $aclnames .= $aclname." "; fwrite ($fd, "\tacl\t\t\t" . $aclname . "\t" . $expr . "\n"); + if ($acl['inspect-delay'] != '') + $inspectdelay = $acl['inspect-delay']; + if ($acl['advancedoptions'] != '') $advancedextra[$acl['syntax']] = $acl['advancedoptions']."\n"; $i++; @@ -1022,6 +1164,8 @@ function haproxy_writeconf($configpath) { if ($default_backend) fwrite ($fd, "\tdefault_backend\t\t" . $default_backend . "\n"); + if ($inspectdelay > 0) + fwrite ($fd, "\ttcp-request inspect-delay\t" . $inspectdelay . "\n"); foreach($advancedextra as $extra) fwrite ($fd, "\t".$extra."\n"); fwrite ($fd, "\n"); @@ -1032,7 +1176,7 @@ function haproxy_writeconf($configpath) { foreach ($a_pendingpl as $pending) { foreach ($a_backends as $pool) { if ($pending['backend'] == $pool['name']) { - write_backend($fd, $pending['name'], $pool, $pending['frontend']); + write_backend($configpath, $fd, $pending['name'], $pool, $pending['frontend']); } } } @@ -1102,21 +1246,19 @@ function haproxy_get_transparent_backends(){ continue; $real_if = get_real_interface($backend["transparent_interface"]); $a_servers = &$backend['ha_servers']['item']; - foreach($a_servers as $server) { - if (is_array($a_servers)) { - - foreach($a_servers as $be) { - if (!$be['status'] == "inactive") - continue; - if (!is_ipaddr($be['address'])) - continue; - $item = array(); - $item['name'] = $be['name']; - $item['interface'] = $real_if; - $item['address'] = $be['address']; - $item['port'] = $be['port']; - $transparent_backends[] = $item; - } + if (is_array($a_servers)) { + foreach($a_servers as $be) { + if (!$be['status'] == "inactive") + continue; + if (!is_ipaddr($be['address'])) + continue; + $item = array(); + $item['name'] = $be['name']; + $item['interface'] = $real_if; + $item['forwardto'] = $be['forwardto']; + $item['address'] = $be['address']; + $item['port'] = $be['port']; + $transparent_backends[] = $item; } } } @@ -1291,13 +1433,16 @@ function killprocesses($processname, $pidfile, $signal = "KILL") { function haproxy_sync_xmlrpc_settings() { global $config; // preserve 'old' sync settings, that should not be overwritten by xmlrpc-sync. + $old_config = $config['installedpackages']['haproxy']; $enable = isset($config['installedpackages']['haproxy']['enablesync']); $config['installedpackages']['haproxy'] = $config['installedpackages']['haproxysyncpkg']; unset($config['installedpackages']['haproxysyncpkg']); + $new_config = &$config['installedpackages']['haproxy']; // restore 'old' settings. $config['installedpackages']['haproxy']['enablesync'] = $enable ? true : false; + $new_config['log-send-hostname'] = $old_config['log-send-hostname']; write_config("haproxy, xmlrpc config synced"); // Write new 'merged' configuration } @@ -1333,22 +1478,28 @@ function haproxy_xmlrpc_sync_configure() { function get_frontend_id($name) { global $config; - $a_backend = &$config['installedpackages']['haproxy']['ha_backends']['item']; + $a_frontend = &$config['installedpackages']['haproxy']['ha_backends']['item']; $i = 0; - foreach($a_backend as $backend) + foreach($a_frontend as $frontend) { - if ($backend['name'] == $name) + if ($frontend['name'] == $name) return $i; $i++; } return null; } +function haproxy_is_frontendname($name) { + if ($name[0] == '!') + $name = substr($name, 1); + return get_frontend_id($name) != null; +} + function get_primaryfrontend($frontend) { global $config; - $a_backend = &$config['installedpackages']['haproxy']['ha_backends']['item']; + $a_frontend = &$config['installedpackages']['haproxy']['ha_backends']['item']; if ($frontend['secondary'] == 'yes') - $mainfrontend = $a_backend[get_frontend_id($frontend['primary_frontend'])]; + $mainfrontend = $a_frontend[get_frontend_id($frontend['primary_frontend'])]; else $mainfrontend = $frontend; return $mainfrontend; @@ -1405,10 +1556,40 @@ function get_haproxy_frontends($excludeitem="") { $result[$frontend['name']]['name'] = "{$frontend['name']} - {$frontend['type']} ({$serveradress})"; $result[$frontend['name']]['ref'] = &$frontend; } - asort($result, SORT_STRING); + uasort($result, haproxy_compareByName); return $result; } +function explode_ports($ports_list) { + $ports_list = split(",", $ports_list); + $result = array(); + foreach($ports_list as $port){ + //$result = array_merge($result,haproxy_portoralias_to_list($port)); + $items = haproxy_portoralias_to_list($port); + foreach($items as $item){ + if (is_portrange($item)) { + $portrange = explode(":", $item); + for($i=$portrange[0];$i<=$portrange[1];$i++) + $result[] = $i; + } else if (is_port($item)) // in pfSense 2.1 is_port returns true for a portrange also.. + $result[] = $item; + } + } + //$result = group_ports($result); << deze maakt er weer portranges van.. maar zoekt wel de unieke. wat dan wel weer handig was. + return $result; +} + +function get_frontend_ports($mainfrontend) { + + $ports = $mainfrontend['port']; + return explode_ports($ports); +} + +function generate_cert_acl($crt, $defaultport, $nondefaultport){ + // The host header send by a browser will contain the portnumber when a nondefault port is used for the server side. + +} + function get_frontend_acls($frontend) { $mainfrontend = get_primaryfrontend($frontend); $result = array(); @@ -1439,17 +1620,28 @@ function get_frontend_acls($frontend) { $poolname = $frontend['backend_serverpool'] . "_" . strtolower($frontend['type']); $aclname = "SNI_" . $poolname; - if ($frontend['ssloffloadacl']){ + //$ports = get_frontend_ports($mainfrontend); + + if ($frontend['ssloffloadacl'] || $frontend['ssloffloadaclnondefault']) { $cert = lookup_cert($frontend['ssloffloadcert']); $cert_cn = cert_get_cn($cert['crt']); $descr = haproxy_escape_acl_name($cert['descr']); unset($cert); + $acl_item = array(); - $acl_item['descr'] = "Certificate ACL ".$cert_cn; - $acl_item['ref'] = array('name' => "{$aclname}_{$descr}",'expression' => 'host_matches', 'value' => $cert_cn); + if ($frontend['ssloffloadacl'] && $frontend['ssloffloadaclnondefault']) { + $acl_item['descr'] = "Certificate ACL match regex: ^{$cert_cn}(:([0-9]){1,5})?$"; + $acl_item['ref'] = array('name' => "{$aclname}_{$descr}",'expression' => 'host_regex', 'value' => "^{$cert_cn}(:([0-9]){1,5})?$"); + } elseif ($frontend['ssloffloadaclnondefault']) { + $acl_item['descr'] = "Certificate ACL starts with: {$cert_cn}:"; + $acl_item['ref'] = array('name' => "{$aclname}_{$descr}",'expression' => 'host_starts_with', 'value' => $cert_cn.":"); + } else { + $acl_item['descr'] = "Certificate ACL match: {$cert_cn}"; + $acl_item['ref'] = array('name' => "{$aclname}_{$descr}",'expression' => 'host_matches', 'value' => $cert_cn); + } $result[] = $acl_item; } - if ($frontend['ssloffloadacladditional']){ + if ($frontend['ssloffloadacladditional'] || $frontend['ssloffloadacladditionalnondefault']) { $certs = $frontend['ha_certificates']['item']; if (is_array($certs)){ foreach($certs as $certref){ @@ -1457,9 +1649,18 @@ function get_frontend_acls($frontend) { $cert_cn = cert_get_cn($cert['crt']); $descr = haproxy_escape_acl_name($cert['descr']); unset($cert); + $acl_item = array(); - $acl_item['descr'] = "Additional certificate ACLs: ".$cert_cn; - $acl_item['ref'] = array('name' => "{$aclname}_{$descr}",'expression' => 'host_matches', 'value' => $cert_cn); + if ($frontend['ssloffloadacladditional'] && $frontend['ssloffloadacladditionalnondefault']) { + $acl_item['descr'] = "Certificate ACL match regex: ^{$cert_cn}(:([0-9]){1,5})?$"; + $acl_item['ref'] = array('name' => "{$aclname}_{$descr}",'expression' => 'host_regex', 'value' => "^({$cert_cn}(($)|(:.*)))"); + } elseif ($frontend['ssloffloadacladditionalnondefault']) { + $acl_item['descr'] = "Certificate ACL starts with: {$cert_cn}:"; + $acl_item['ref'] = array('name' => "{$aclname}_{$descr}",'expression' => 'host_starts_with', 'value' => $cert_cn.":"); + } else { + $acl_item['descr'] = "Certificate ACL match: {$cert_cn}"; + $acl_item['ref'] = array('name' => "{$aclname}_{$descr}",'expression' => 'host_matches', 'value' => $cert_cn); + } $result[] = $acl_item; } } @@ -1468,18 +1669,43 @@ function get_frontend_acls($frontend) { return $result; } -function get_backend($name) { +function get_backend_id($name) { global $config; $a_backend = &$config['installedpackages']['haproxy']['ha_pools']['item']; + $i = 0; if(is_array($a_backend)) - foreach($a_backend as $key => $backend) - { + foreach($a_backend as $key => $backend) { if ($backend['name'] == $name) - return $backend; + return $i; + $i++; } return null; } +function get_backend($name) { + global $config; + $a_backend = &$config['installedpackages']['haproxy']['ha_pools']['item']; + $id = get_backend_id($name); + if (is_numeric($id)) + return $a_backend[$id]; + return null; +} + +function use_frontend_as_unixsocket($name) { + global $config; + $a_backends = &$config['installedpackages']['haproxy']['ha_pools']['item']; + foreach ($a_backends as $backend) { + $a_servers = &$backend['ha_servers']['item']; + if (is_array($a_servers)) { + foreach($a_servers as $server) { + if ($server['forwardto'] && $server['forwardto'] == $name) + return true; + } + } + } + return false; +} + function haproxy_escapestring($configurationsting) { $result = str_replace('\\', '\\\\', $configurationsting); $result = str_replace(' ', '\\ ', $result); diff --git a/config/haproxy-devel/haproxy_global.php b/config/haproxy-devel/haproxy_global.php index 50472d9f..cad3795a 100755 --- a/config/haproxy-devel/haproxy_global.php +++ b/config/haproxy-devel/haproxy_global.php @@ -36,7 +36,7 @@ require_once("haproxy_utils.inc"); require_once("globals.inc"); require_once("pkg_haproxy_tabs.inc"); -$simplefields = array('localstats_refreshtime','localstats_sticktable_refreshtime'); +$simplefields = array('localstats_refreshtime','localstats_sticktable_refreshtime','log-send-hostname','ssldefaultdhparam'); if (!is_array($config['installedpackages']['haproxy'])) $config['installedpackages']['haproxy'] = array(); @@ -266,55 +266,6 @@ function enable_change(enable_change) { Checking this option will interupt existing connections on a restart. (which happens when the configuration is applied, but possibly also when pfSense detects an interface comming up or changing its ip-address) - - - Remote syslog host - - -
- To log to the local pfSense systemlog fill the host with the value /var/run/log, however if a lot of messages are generated logging is likely to be incomplete. (Also currently no informational logging gets shown in the systemlog.) - - - - - Syslog facility - - - - - - - - Syslog level - - - - - Carp monitor @@ -373,6 +324,77 @@ function enable_change(enable_change) { size="10" maxlength="5" /> Seconds, Leave this setting empty to not refresh the page automatically. EXAMPLE: 10 + + Logging + + + + Remote syslog host + + +
+ To log to the local pfSense systemlog fill the host with the value /var/run/log, however if a lot of messages are generated logging is likely to be incomplete. (Also currently no informational logging gets shown in the systemlog.) + + + + + Syslog facility + + + + + + + + Syslog level + + + + + + + Log hostname + + size="18" maxlength="50" /> EXAMPLE: HaproxyMasterNode
Sets the hostname field in the syslog header. If empty defaults to the system hostname. + + + + Tuning + + + Max SSL Diffie-Hellman size + + size="10" maxlength="5" /> EXAMPLE: 2048
Sets the maximum size of the Diffie-Hellman parameters used for generating +the ephemeral/temporary Diffie-Hellman key in case of DHE key exchange. +Minimum and default value is: 1024, bigger values might increase CPU usage.
+ For more information about the "tune.ssl.default-dh-param" option please see HAProxy Documentation
+ NOTE: HAProxy will emit a warning when starting when this setting is used but not configured. + + Global Advanced pass thru diff --git a/config/haproxy-devel/haproxy_htmllist.inc b/config/haproxy-devel/haproxy_htmllist.inc index ae46ffd4..effc6ae8 100644 --- a/config/haproxy-devel/haproxy_htmllist.inc +++ b/config/haproxy-devel/haproxy_htmllist.inc @@ -50,7 +50,30 @@ function haproxy_htmllist_get_values($html_list){ return $values; } -function haproxy_htmllist($tablename,$values,$items,$editstate=false){ +function haproxy_htmllist_drawcell($item, $itemvalue, $editable, $itemnamenr = "") { + $itemtype = $item['type']; + if ($editable) { + $itemtype = $item['type']; + if ($itemtype == "select"){ + echo_html_select($itemnamenr, $item['items'], $itemvalue,"","updatevisibility();", "width:{$item['size']}"); + } else + if ($itemtype == "checkbox"){ + $checked = $itemvalue=='yes' ? " checked" : ""; + echo ""; + } else + echo ""; + } else { + if ($itemtype == "select"){ + echo $item['items'][$itemvalue]['name']; + } else + if ($itemtype == "checkbox"){ + echo $itemvalue=='yes' ? gettext('yes') : gettext('no'); + } else + echo $itemvalue; + } +} + +function haproxy_htmllist($tablename,$rowvalues,$items,$editstate=false,$itemdetails=null){ global $g, $counter; echo ""; @@ -59,24 +82,20 @@ function haproxy_htmllist($tablename,$values,$items,$editstate=false){ } echo ""; - if (is_array($values)){ - foreach($values as $value){ + if (is_array($rowvalues)){ + foreach($rowvalues as $value){ if (!$editstate) { echo ""; $leftitem = true; - foreach($items as $item){ + foreach($items as $item) { $tdclass = $leftitem ? "vtable listlr" : "vtable listr"; echo ""; $leftitem = false; } @@ -94,24 +113,18 @@ function haproxy_htmllist($tablename,$values,$items,$editstate=false){
"; $itemname = $item['name']; - $itemtype = $item['type']; $itemvalue = $value[$itemname]; - if ($itemtype == "select"){ - echo $item['items'][$itemvalue]['name']; + if (isset($item['customdrawcell'])) { + $item['customdrawcell']($item, $itemvalue, false); } else - if ($itemtype == "checkbox"){ - echo $itemvalue=='yes' ? gettext('yes') : gettext('no'); - } else - echo $itemvalue; + haproxy_htmllist_drawcell($item, $itemvalue, false); echo "
"; echo ""; - } + } $displaystyle = $editstate ? "" : "display: none;"; echo ""; foreach($items as $item){ $itemname = $item['name']; - $itemtype = $item['type']; $itemvalue = $value[$itemname]; - $itemnamenr = $itemname.$counter; + $itemnamenr = $itemname . $counter; echo ""; - if ($itemtype == "select"){ - echo_html_select($itemnamenr, $item['items'], $itemvalue,"","updatevisibility();", "width:{$item['size']}"); + if (isset($item['customdrawcell'])) { + $item['customdrawcell']($item, $itemvalue, true, $itemnamenr); } else - if ($itemtype == "checkbox"){ - $checked = $itemvalue=='yes' ? " checked" : ""; - echo ""; - - } else - echo ""; + haproxy_htmllist_drawcell($item, $itemvalue, true, $itemnamenr); echo ""; } echo " @@ -125,6 +138,78 @@ function haproxy_htmllist($tablename,$values,$items,$editstate=false){ "; echo ""; + if (isset($itemdetails)) { + $colspan = count($items)-1; + echo ""; + ?> + +
+ ')"> + _details_off" alt="Expand advanced server settings" + src="tree/plus.gif" style="clip:rect(19px 13px 30px 2px); top:-19px;position:absolute;"/> + +
+ + "; + $itemnr = 0; + echo "
"; + $itemcount = count($itemdetails); + foreach($itemdetails as $item) { + echo "
"; + $tdclass = "";//$leftitem ? "vtable listlr" : "vtable listr"; + echo $item['columnheader'] . ": "; + $itemname = $item['name']; + $itemvalue = $value[$itemname]; + if (isset($item['customdrawcell'])) { + $item['customdrawcell']($item, $itemvalue, false); + } else + haproxy_htmllist_drawcell($item, $itemvalue, false); + $leftitem = false; + $itemnr++; + if ($itemcount != $itemnr) + echo ", "; + echo "
"; + } + echo "
"; + echo ""; + echo ""; + echo ""; + } + if (isset($itemdetails)) { + $colspan = count($items)-1; + echo ""; + echo " "; + echo ""; + echo ""; + echo ""; + } + + + $counter++; } } @@ -136,10 +221,10 @@ function haproxy_htmllist($tablename,$values,$items,$editstate=false){ function haproxy_htmllist_js(){ ?> "; + if (strtolower($frontend['type']) == "http" && $frontend['ssloffload']) { $cert = lookup_cert($frontend['ssloffloadcert']); $descr = htmlspecialchars($cert['descr']); @@ -166,15 +174,6 @@ include("head.inc"); echo 'SSL offloading'; } - $acls = get_frontend_acls($frontend); - $isaclset = ""; - foreach ($acls as $acl) { - $isaclset .= " " . htmlspecialchars($acl['descr']); - } - - if ($isaclset) - echo ""; - $isadvset = ""; if ($frontend['advanced_bind']) $isadvset .= "Advanced bind: ".htmlspecialchars($frontend['advanced_bind'])."\r\n"; if ($frontend['advanced']) $isadvset .= "Advanced pass thru setting used\r\n"; @@ -188,7 +187,10 @@ include("head.inc"); $backend_serverpool_hint = gettext("Servers in pool:"); if (is_array($servers)){ foreach($servers as $server){ - $backend_serverpool_hint .= "\n".$server['address'].":".$server['port']; + if ($server['forwardto'] && $server['forwardto'] != "") + $backend_serverpool_hint .= "\n[".$server['forwardto']."]"; + else + $backend_serverpool_hint .= "\n".$server['address'].":".$server['port']; } } } @@ -208,7 +210,9 @@ include("head.inc");
+ +
diff --git a/config/haproxy-devel/haproxy_listeners_edit.php b/config/haproxy-devel/haproxy_listeners_edit.php index 78423f6d..d243ffb1 100644 --- a/config/haproxy-devel/haproxy_listeners_edit.php +++ b/config/haproxy-devel/haproxy_listeners_edit.php @@ -52,7 +52,7 @@ function haproxy_js_acl_select($mode) { $seltext = ''; foreach ($a_acltypes as $key => $expr) { if ($expr['mode'] == '' || $expr['mode'] == $mode) - $seltext .= "