[__haproxy_dualstack] New type with PROXY protocol support

This is backwards compatible with what is already used internally @ungleich, but
adds on top of that the ability to customise ports and, most importantly, it
adds PROXY protocol support.
This commit is contained in:
evilham 2021-10-30 12:34:14 +02:00
parent 2038244ec4
commit 1d867f4778
9 changed files with 323 additions and 0 deletions

View file

@ -0,0 +1,8 @@
frontend http
bind BIND@:80
mode http
option httplog
default_backend http
backend http
mode http

View file

@ -0,0 +1,10 @@
frontend https
bind BIND@:443
mode tcp
option tcplog
tcp-request inspect-delay 5s
tcp-request content accept if { req_ssl_hello_type 1 }
default_backend https
backend https
mode tcp

View file

@ -0,0 +1,12 @@
frontend imaps
bind BIND@:143
bind BIND@:993
mode tcp
option tcplog
tcp-request inspect-delay 5s
tcp-request content accept if { req_ssl_hello_type 1 }
default_backend imaps
backend imaps
mode tcp

View file

@ -0,0 +1,12 @@
frontend smtps
bind BIND@:25
bind BIND@:465
mode tcp
option tcplog
tcp-request inspect-delay 5s
tcp-request content accept if { req_ssl_hello_type 1 }
default_backend smtps
backend smtps
mode tcp

View file

@ -0,0 +1,122 @@
cdist-type__haproxy_dualstack(7)
================================
NAME
----
cdist-type__haproxy_dualstack - Proxy services from a dual-stack server
DESCRIPTION
-----------
This (singleton) type installs and configures haproxy to act as a dual-stack
proxy for single-stack services.
This can be useful to add IPv4 support to IPv6-only services while only using
one IPv4 for many such services.
By default this type uses the plain TCP proxy mode, which means that there is no
need for TLS termination on this host when SNI is supported.
This also means that proxied service will not receive the client's IP address,
but will see the proxy's IP address instead (that of `$__target_host`).
This can be solved by using the PROXY protocol, but do take into account that,
e.g. nginx cannot serve both regular HTTP(S) and PROXY protocols on the same
port, so you will need to use other ports for that.
As a recommendation in this type: use TCP ports 8080 and 591 respectively to
serve HTTP and HTTPS using the PROXY protocol.
See the EXAMPLES for more details.
OPTIONAL PARAMETERS
-------------------
v4proxy
Proxy incoming IPv4 connections to the equivalent IPv6 endpoint.
In its simplest use, it must be a NAME with an `AAAA` DNS entry, which is
the IP address actually providing the proxied services.
The full format of this argument is:
`[proxy:]NAME[[:PROTOCOL_1=PORT_1]...[:PROTOCOL_N=PORT_N]]`
Where starting with `proxy:` determines that the PROXY protocol must be
used and each `:PROTOCOL=PORT` (e.g. `:http=8080` or `:https=591`) is a PORT
override for the given PROTOCOL (see `--protocol`), if not present the
PROTOCOL's default port will be used.
v6proxy
Proxy incoming IPv6 connections to the equivalent IPv4 endpoint.
In its simplest use, it must be a NAME with an `A` DNS entry, which is
the IP address actually providing the proxied services.
See `--v4proxy` for more options and details.
protocol
Can be passed multiple times or as a space-separated list of protocols.
Currently supported protocols are: `http`, `https`, `imaps`, `smtps`.
This defaults to: `http https imaps smtps`.
EXAMPLES
--------
.. code-block:: sh
# Proxy the IPv6-only services so IPv4-only clients can access them
# This uses HAProxy's TCP mode for http, https, imaps and smtps
__haproxy_dualstack \
--v4proxy ipv6.chat \
--v4proxy matrix.ungleich.ch
# Proxy the IPv6-only HTTP(S) services so IPv4-only clients can access them
# Note this means that the backend IPv6-only server will only see
# the IPv6 address of the haproxy host managed by cdist, which can be
# troublesome if this information is relevant for analytics/security/...
# See the PROXY example below
__haproxy_dualstack \
--protocol http --protocol https \
--v4proxy ipv6.chat \
--v4proxy matrix.ungleich.ch
# Use the PROXY protocol to proxy the IPv6-only HTTP(S) services enabling
# IPv4-only clients to access them while maintaining the client's IP address
__haproxy_dualstack \
--protocol http --protocol https \
--v4proxy proxy:ipv6.chat:http=8080:https=591 \
--v4proxy proxy:matrix.ungleich.ch:http=8080:https=591
# Note however that the PROXY protocol is not compatible with regular
# HTTP(S) protocols, so your nginx will have to listen on different ports
# with the PROXY settings.
# Note that you will need to restrict access to the 8080 port to prevent
# Client IP spoofing.
# This can be something like:
# server {
# # listen for regular HTTP connections
# listen [::]:80 default_server;
# listen 80 default_server;
# # listen for PROXY HTTP connections
# listen [::]:8080 proxy_protocol;
# # Accept the Client's IP from the PROXY protocol
# real_ip_header proxy_protocol;
# }
SEE ALSO
--------
- https://www.haproxy.com/blog/haproxy/proxy-protocol/
- https://docs.nginx.com/nginx/admin-guide/load-balancer/using-proxy-protocol/
- https://www.haproxy.com/blog/enhanced-ssl-load-balancing-with-server-name-indication-sni-tls-extension/
AUTHORS
-------
ungleich <foss--@--ungleich.ch>
Evilham <cvs--@--evilham.com>
COPYING
-------
Copyright \(C) 2021 ungleich glarus ag. You can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

View file

@ -0,0 +1,155 @@
#!/bin/sh -eu
__package haproxy
require="__package/haproxy" __start_on_boot haproxy
tmpdir="$__object/files"
mkdir "$tmpdir"
configtmp="$__object/files/haproxy.cfg"
os=$(cat "$__global/explorer/os")
case $os in
freebsd)
CONFIG_FILE="/usr/local/etc/haproxy.conf"
cat <<EOF > "$configtmp"
global
maxconn 4000
user nobody
group nogroup
daemon
EOF
;;
*)
CONFIG_FILE="/etc/haproxy/haproxy.cfg"
cat <<EOF > "$configtmp"
global
log [::1] local2
chroot /var/lib/haproxy
pidfile /var/run/haproxy.pid
maxconn 4000
user haproxy
group haproxy
daemon
# turn on stats unix socket
stats socket /var/lib/haproxy/stats
EOF
;;
esac
cat <<EOF >> "$configtmp"
defaults
retries 3
log global
timeout http-request 10s
timeout queue 1m
timeout connect 10s
timeout client 1m
timeout server 1m
timeout http-keep-alive 10s
timeout check 10s
EOF
dig_cmd="$(command -v dig || true)"
get_ip() {
# Usage: get_ip (ipv4|ipv6) NAME
# uses "dig" if available, else fallback to "host"
case $1 in
ipv4)
if [ -n "${dig_cmd}" ]; then
${dig_cmd} +short A "$2"
else
host -t A "$2" | cut -d ' ' -f 4 | grep -v 'found:'
fi
;;
ipv6)
if [ -n "${dig_cmd}" ]; then
${dig_cmd} +short AAAA "$2"
else
host -t AAAA "$2" | cut -d ' ' -f 5 | grep -v 'NXDOMAIN'
fi
;;
esac
}
PROTOCOLS="$(cat "$__object/parameter/protocol")"
for proxy in v4proxy v6proxy; do
param=$__object/parameter/$proxy
# no backend? skip generating code
if [ ! -f "$param" ]; then
continue
fi
# turn backend name into bind parameter: v4backend -> ipv4@
bind=$(echo $proxy | sed -e 's/^/ip/' -e 's/proxy//')
case $bind in
ipv4)
backendproto=ipv6
;;
ipv6)
backendproto=ipv4
;;
esac
for proto in ${PROTOCOLS}; do
# Add protocol "header"
printf "\n# %s %s \n" "${bind}" "${proto}" >> "$configtmp"
sed -e "s/BIND/$bind/" \
-e "s/\(frontend[[:space:]].*\)/\1$bind/" \
-e "s/\(backend[[:space:]].*\)/\\1$bind/" \
"$__type/files/$proto" >> "$configtmp"
while read -r hostdefinition; do
if echo "$hostdefinition" | grep -qE '^proxy:'; then
# Proxy protocol was requested
host="$(echo "$hostdefinition" | sed -E 's/^proxy:([^:]+).*$/\1/')"
send_proxy=" send-proxy"
else
# Just use tcp proxy mode
host="$hostdefinition"
send_proxy=""
fi
if echo "$hostdefinition" | grep -qE ":${proto}="; then
# Use custom port definition if requested
port="$(echo "$hostdefinition" | sed -E "s/^(.*:)?${proto}=([0-9]+).*$/:\2/")"
else
# Else use the default
port=""
fi
servername=$host
res=$(get_ip "$bind" "$servername")
if [ -z "$res" ]; then
echo "$servername does not resolve - aborting config" >&2
exit 1
fi
# Treat protocols without TLS+SNI specially
if [ "$proto" = http ]; then
echo " use-server $servername if { hdr(host) -i $host }" >> "$configtmp"
else
echo " use-server $servername if { req_ssl_sni -i $host }" >> "$configtmp"
fi
# Create the "server" itself.
# Note that port and send_proxy will be empty unless
# they were requested by the type user
echo " server $servername ${backendproto}@${host}${port}${send_proxy}" >> "$configtmp"
done < "$param"
done
done
# Create config file
require="__package/haproxy" __file ${CONFIG_FILE} --source "$configtmp" --mode 0644
require="__file${CONFIG_FILE}" __check_messages "haproxy_reload" \
--pattern "^__file${CONFIG_FILE}" \
--execute "service haproxy reload || service haproxy restart"

View file

@ -0,0 +1 @@
http https imaps smtps

View file

@ -0,0 +1,3 @@
protocol
v4proxy
v6proxy

View file