commit 29b6a57ca23107846cf5b7f04c31e92a5abbde7c Author: Mario Fetka Date: Wed Feb 12 11:53:48 2020 +0100 Imported Upstream version 0.2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6cab3ea --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.libs +mod_proxy_protocol.la +mod_proxy_protocol.lo +mod_proxy_protocol.slo diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c0ff43b --- /dev/null +++ b/Makefile @@ -0,0 +1,33 @@ +## +## Makefile -- Build procedure for sample proxy_protocol Apache module +## Autogenerated via ``apxs -n proxy_protocol -g''. +## + +# the used tools +APXS=apxs +XSLT=xsltproc + +# the default target +all: .libs/mod_proxy_protocol.so + +# build the so in the current directory +.libs/mod_proxy_protocol.so: mod_proxy_protocol.c + $(APXS) -c -Wc,-Wall mod_proxy_protocol.c + +# install the so - usually needs root access +install: .libs/mod_proxy_protocol.so + $(APXS) -i mod_proxy_protocol.la + +# generate the html doc +docs: mod_proxy_protocol.html + +mod_proxy_protocol.html: mod_proxy_protocol.xml mod_proxy_protocol.xml.meta + $(XSLT) -o $@ $< + +# generate packages +dpkg: + debuild --no-tgz-check -uc -us + +# cleanup +clean: + -rm -rf mod_proxy_protocol.o mod_proxy_protocol.lo mod_proxy_protocol.slo mod_proxy_protocol.la .libs mod_proxy_protocol.html diff --git a/README.md b/README.md new file mode 100644 index 0000000..eebeb68 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# Apache Proxy Protocol Module + +This is an [Apache](http://httpd.apache.org/) module that implements the +server side of HAProxy's +[Proxy Protocol](http://blog.haproxy.com/haproxy/proxy-protocol/). + +Note: as of Apache 2.4.30 this code has been merged into +[mod_remoteip](https://httpd.apache.org/docs/2.4/mod/mod_remoteip.html), +with the `ProxyProtocol` directive renamed to `RemoteIPProxyProtocol`. + +## Build and Install + +You'll need the apache development packages installed (typically something +like `apache-devel` or `apache2-dev`). Then simply running `make` will +create the shared library in `.libs/mod_proxy_protocol.so`; `make install` +will attempt to install it in your current apache installation (you'll +probably need to be root for this). + +## Configuration + +Add the following directive to your apache config to load the module + + LoadModule proxy_protocol_module /mod_proxy_protocol.so + +or try running + + apxs -a mod_proxy_protocol.c + +For configuration details see the +[module docs](http://roadrunner2.github.io/mod-proxy-protocol/mod_proxy_protocol.html) + +## Amazon EC2 Notes + +To properly secure Apache when using this on EC2 behind an ELB you'll probably +want to do something like the following to ensure that only the ELB can +provide the proxy protocol header while still being able to access the site +directly (not via the ELB): + +1. In Apache create a copy of virtual host with a new port, and add the + 'ProxyProtocol On' directive to this new virtual host. +2. Add an entry to the security group for the server that only allows access + to this new port from the ELB (specify the source as `amazon-elb/amazon-elb-sg`) +3. Point the ELB listener at this new port (and of course + [enable](http://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/enable-proxy-protocol.html) + the proxy protocol for that) + +## TODO + +* Add access-control (see commented out `ProxyProtocolTrustedProxies` in + `mod_proxy_protocol.xml`) +* Add support for outgoing connections (`mod_proxy`) + diff --git a/debian/README.Debian b/debian/README.Debian new file mode 100644 index 0000000..8656717 --- /dev/null +++ b/debian/README.Debian @@ -0,0 +1,5 @@ +## To activate mod_proxy_protocol, do: + +a2enmod proxy_protocol && /etc/init.d/apache2 force-reload + +## And then enable ProxyProtocol for the desired hosts. diff --git a/debian/apache2 b/debian/apache2 new file mode 100644 index 0000000..312ccd2 --- /dev/null +++ b/debian/apache2 @@ -0,0 +1,3 @@ +mod .libs/mod_proxy_protocol.so +mod debian/proxy_protocol.load +mod debian/proxy_protocol.conf diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..47bcabd --- /dev/null +++ b/debian/changelog @@ -0,0 +1,11 @@ +mod-proxy-protocol (0.2-1) xenial; urgency=low + + * Merged patch to fix http/2 issues. + + -- Roadrunner2 Tue, 20 Mar 2018 02:11:49 -0700 + +mod-proxy-protocol (0.1-1) trusty; urgency=low + + * Initial release + + -- Roadrunner2 Thu, 30 Oct 2014 16:46:21 -0700 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..ec63514 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +9 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..596d88b --- /dev/null +++ b/debian/control @@ -0,0 +1,17 @@ +Source: mod-proxy-protocol +Section: web +Priority: optional +Maintainer: Roadrunner2 +Build-Depends: debhelper (>= 7.0.50~), apache2-dev (>= 2.4.0), xsltproc +Standards-Version: 3.8.4 +Homepage: https://github.com/roadrunner2/mod-proxy-protocol + +Package: libapache2-mod-proxy-protocol +Architecture: amd64 +Depends: ${shlibs:Depends}, ${misc:Depends}, apache2-api-20120211 +Description: Apache module for proxy protocol + The proxy protocol is a way for upstream proxies and load-balancers to + for the ip-address of the client to the server. This package contains + an Apache module that implements the server-side of this protocol, thereby + allowing other modules to see and use actual client's ip-address instead + of that of the upstream proxy/load-balancer. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..ad475f6 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,13 @@ +Copyright 2014 Cloudzilla Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/debian/dirs b/debian/dirs new file mode 100644 index 0000000..8e8d38b --- /dev/null +++ b/debian/dirs @@ -0,0 +1 @@ +usr/lib/apache2/modules diff --git a/debian/docs b/debian/docs new file mode 100644 index 0000000..e980146 --- /dev/null +++ b/debian/docs @@ -0,0 +1,4 @@ +README.md +mod_proxy_protocol.xml +mod_proxy_protocol.xml.meta +debian/README.Debian diff --git a/debian/lintian-overrides b/debian/lintian-overrides new file mode 100644 index 0000000..48369ea --- /dev/null +++ b/debian/lintian-overrides @@ -0,0 +1,2 @@ +libapache2-mod-proxy-protocol: apache2-module-depends-on-real-apache2-package +libapache2-mod-proxy-protocol: copyright-should-refer-to-common-license-file-for-apache-2 diff --git a/debian/proxy_protocol.conf b/debian/proxy_protocol.conf new file mode 100644 index 0000000..f010951 --- /dev/null +++ b/debian/proxy_protocol.conf @@ -0,0 +1,3 @@ + + #ProxyProtocol On + diff --git a/debian/proxy_protocol.load b/debian/proxy_protocol.load new file mode 100644 index 0000000..e1069f0 --- /dev/null +++ b/debian/proxy_protocol.load @@ -0,0 +1 @@ +LoadModule proxy_protocol_module /usr/lib/apache2/modules/mod_proxy_protocol.so diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..f311f7d --- /dev/null +++ b/debian/rules @@ -0,0 +1,43 @@ +#!/usr/bin/make -f + +# debian rules file for mod_proxy_protocol + +build: + dh_testdir + make all # docs + +clean: + dh_testdir + dh_testroot + make clean + dh_clean + +install: build + dh_testdir + dh_testroot + dh_prep + dh_installdirs + dh_install + dh_apache2 -e + +# Build architecture-dependent files here. +binary-arch: build install + dh_testdir + dh_testroot + dh_installdocs + dh_installchangelogs + dh_link + dh_strip + dh_compress + dh_fixperms + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb + +# Build architecture-independent files here. +binary-indep: build install + +binary: binary-arch binary-indep +.PHONY: build clean binary-indep binary-arch binary install configure diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +1.0 diff --git a/mod_proxy_protocol.c b/mod_proxy_protocol.c new file mode 100644 index 0000000..e74fdf4 --- /dev/null +++ b/mod_proxy_protocol.c @@ -0,0 +1,797 @@ +/* + * Copyright 2014 Cloudzilla Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * mod_proxy_protocol.c -- Apache proxy_protocol module + * + * This implements the server side of the proxy protocol decribed in + * http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt . It works + * by installing itself (where enabled) as a connection filter (ahead of + * mod_ssl) to parse and remove the proxy protocol header, and by then + * modifying the useragent_* fields in the requests accordingly. + * + * TODO: + * * add the following configs: + * ProxyProtocolTrustedProxies "all"|ip-addr|host [ip-addr|host] ... (default all) + * ProxyProtocolRejectUntrusted Yes|No (default Yes) + * What to do if a connection is received from an untrusted proxy: + * yes = abort the connection + * no = allow connection and remove header, but ignore header + * * add support for sending the header on outgoing connections (mod_proxy), + * and config for choosing which hosts to enable it for + * (ProxyProtocolDownstreamHosts?) + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_protocol.h" +#include "http_connection.h" +#include "http_main.h" +#include "http_log.h" +#include "ap_config.h" +#include "ap_listen.h" +#include "apr_strings.h" +#include "apr_version.h" + +module AP_MODULE_DECLARE_DATA proxy_protocol_module; + +/* + * Module configuration + */ + +typedef struct pp_addr_info { + struct pp_addr_info *next; + apr_sockaddr_t *addr; + server_rec *source; +} pp_addr_info; + +typedef struct { + pp_addr_info *enabled; + pp_addr_info *disabled; + apr_pool_t *pool; +} pp_config; + +static int pp_hook_pre_config(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp) +{ + pp_config *conf; + + conf = (pp_config *) apr_palloc(pconf, sizeof(pp_config)); + conf->enabled = NULL; + conf->disabled = NULL; + conf->pool = pconf; + + ap_set_module_config(ap_server_conf->module_config, &proxy_protocol_module, + conf); + + return OK; +} + +#if !(APR_VERSION_AT_LEAST(1,5,0)) +static APR_DECLARE(int) apr_sockaddr_is_wildcard(const apr_sockaddr_t *addr) +{ + static const char inaddr_any[ +#if APR_HAVE_IPV6 + sizeof(struct in6_addr) +#else + sizeof(struct in_addr) +#endif + ] = {0}; + + if (addr->ipaddr_ptr /* IP address initialized */ + && addr->ipaddr_len <= sizeof inaddr_any) { /* else bug elsewhere? */ + if (!memcmp(inaddr_any, addr->ipaddr_ptr, addr->ipaddr_len)) { + return 1; + } +#if APR_HAVE_IPV6 + if (addr->family == AF_INET6 + && IN6_IS_ADDR_V4MAPPED((struct in6_addr *)addr->ipaddr_ptr)) { + struct in_addr *v4 = (struct in_addr *)&((apr_uint32_t *)addr->ipaddr_ptr)[3]; + + if (!memcmp(inaddr_any, v4, sizeof *v4)) { + return 1; + } + } +#endif + } + return 0; +} +#endif + +/* Similar apr_sockaddr_equal, except that it compares ports too. */ +static int pp_sockaddr_equal(apr_sockaddr_t *addr1, apr_sockaddr_t *addr2) +{ + return (addr1->port == addr2->port && apr_sockaddr_equal(addr1, addr2)); +} + +/* Similar pp_sockaddr_equal, except that it handles wildcard addresses + * and ports too. + */ +static int pp_sockaddr_compat(apr_sockaddr_t *addr1, apr_sockaddr_t *addr2) +{ + /* test exact address equality */ + if (apr_sockaddr_equal(addr1, addr2) && + (addr1->port == addr2->port || addr1->port == 0 || addr2->port == 0)) { + return 1; + } + + /* test address wildcards */ + if (apr_sockaddr_is_wildcard(addr1) && + (addr1->port == 0 || addr1->port == addr2->port)) { + return 1; + } + + if (apr_sockaddr_is_wildcard(addr2) && + (addr2->port == 0 || addr2->port == addr1->port)) { + return 1; + } + + return 0; +} + +static int pp_addr_in_list(pp_addr_info *list, apr_sockaddr_t *addr) +{ + for (; list; list = list->next) { + if (pp_sockaddr_compat(list->addr, addr)) { + return 1; + } + } + + return 0; +} + +static void pp_warn_enable_conflict(pp_addr_info *prev, server_rec *new, int on) +{ + char buf[INET6_ADDRSTRLEN]; + + apr_sockaddr_ip_getbuf(buf, sizeof(buf), prev->addr); + + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, new, + "ProxyProtocol: previous setting for %s:%hu from virtual " + "host {%s:%hu in %s} is being overriden by virtual host " + "{%s:%hu in %s}; new setting is '%s'", + buf, prev->addr->port, prev->source->server_hostname, + prev->source->addrs->host_port, prev->source->defn_name, + new->server_hostname, new->addrs->host_port, new->defn_name, + on ? "On" : "Off"); +} + +static const char *pp_enable_proxy_protocol(cmd_parms *cmd, void *config, + int flag) +{ + pp_config *conf; + server_addr_rec *addr; + pp_addr_info **add; + pp_addr_info **rem; + pp_addr_info *list; + + conf = ap_get_module_config(ap_server_conf->module_config, + &proxy_protocol_module); + + if (flag) { + add = &conf->enabled; + rem = &conf->disabled; + } + else { + add = &conf->disabled; + rem = &conf->enabled; + } + + for (addr = cmd->server->addrs; addr; addr = addr->next) { + /* remove address from opposite list */ + if (*rem) { + if (pp_sockaddr_equal((*rem)->addr, addr->host_addr)) { + pp_warn_enable_conflict(*rem, cmd->server, flag); + *rem = (*rem)->next; + } + else { + for (list = *rem; list->next; list = list->next) { + if (pp_sockaddr_equal(list->next->addr, addr->host_addr)) { + pp_warn_enable_conflict(list->next, cmd->server, flag); + list->next = list->next->next; + break; + } + } + } + } + + /* add address to desired list */ + if (!pp_addr_in_list(*add, addr->host_addr)) { + pp_addr_info *info = apr_palloc(conf->pool, sizeof(*info)); + info->addr = addr->host_addr; + info->source = cmd->server; + info->next = *add; + *add = info; + } + } + + return NULL; +} + +static int pp_hook_post_config(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + pp_config *conf; + pp_addr_info *info; + char buf[INET6_ADDRSTRLEN]; + + conf = ap_get_module_config(ap_server_conf->module_config, + &proxy_protocol_module); + + for (info = conf->enabled; info; info = info->next) { + apr_sockaddr_ip_getbuf(buf, sizeof(buf), info->addr); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, + "ProxyProtocol: enabled on %s:%hu", buf, info->addr->port); + } + for (info = conf->disabled; info; info = info->next) { + apr_sockaddr_ip_getbuf(buf, sizeof(buf), info->addr); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, + "ProxyProtocol: disabled on %s:%hu", buf, info->addr->port); + } + + return OK; +} + +static const command_rec proxy_protocol_cmds[] = { + AP_INIT_FLAG("ProxyProtocol", pp_enable_proxy_protocol, NULL, RSRC_CONF, + "Enable proxy-protocol handling (`on', `off')"), + { NULL } +}; + +/* + * Proxy-protocol implementation + */ + +static const char *pp_inp_filter = "ProxyProtocol Filter"; + +typedef struct { + char line[108]; +} proxy_v1; + +typedef union { + struct { /* for TCP/UDP over IPv4, len = 12 */ + uint32_t src_addr; + uint32_t dst_addr; + uint16_t src_port; + uint16_t dst_port; + } ip4; + struct { /* for TCP/UDP over IPv6, len = 36 */ + uint8_t src_addr[16]; + uint8_t dst_addr[16]; + uint16_t src_port; + uint16_t dst_port; + } ip6; + struct { /* for AF_UNIX sockets, len = 216 */ + uint8_t src_addr[108]; + uint8_t dst_addr[108]; + } unx; +} proxy_v2_addr; + +typedef struct { + uint8_t sig[12]; /* hex 0D 0A 0D 0A 00 0D 0A 51 55 49 54 0A */ + uint8_t ver_cmd; /* protocol version and command */ + uint8_t fam; /* protocol family and address */ + uint16_t len; /* number of following bytes part of the header */ + proxy_v2_addr addr; +} proxy_v2; + +typedef union { + proxy_v1 v1; + proxy_v2 v2; +} proxy_header; + +static const char v2sig[12] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"; +#define MIN_V1_HDR_LEN 15 +#define MIN_V2_HDR_LEN 16 +#define MIN_HDR_LEN MIN_V1_HDR_LEN + +typedef struct { + char header[sizeof(proxy_header)]; + apr_size_t rcvd; + apr_size_t need; + int version; + ap_input_mode_t mode; + apr_bucket_brigade *bb; + int done; +} pp_filter_context; + +typedef struct { + apr_sockaddr_t *client_addr; + char *client_ip; +} pp_conn_config; + +static int pp_is_server_port(apr_port_t port) +{ + ap_listen_rec *lr; + + for (lr = ap_listeners; lr; lr = lr->next) { + if (lr->bind_addr && lr->bind_addr->port == port) { + return 1; + } + } + + return 0; +} + +/* Add our filter to the connection. + */ +static int pp_hook_pre_connection(conn_rec *c, void *csd) +{ + pp_config *conf; + pp_conn_config *conn_conf; + + /* Establish master config in slave connections, so that request + * processing finds it. */ + if (c->master != NULL) { + conn_conf = ap_get_module_config(c->master->conn_config, + &proxy_protocol_module); + if (conn_conf) { + ap_set_module_config(c->conn_config, &proxy_protocol_module, + conn_conf); + } + return DECLINED; + } + + /* check if we're enabled for this connection */ + conf = ap_get_module_config(ap_server_conf->module_config, + &proxy_protocol_module); + + if (!pp_addr_in_list(conf->enabled, c->local_addr) || + pp_addr_in_list(conf->disabled, c->local_addr)) { + return DECLINED; + } + + /* mod_proxy creates outgoing connections - we don't want those */ + if (!pp_is_server_port(c->local_addr->port)) { + return DECLINED; + } + + /* add our filter */ + if (!ap_add_input_filter(pp_inp_filter, NULL, NULL, c)) { + return DECLINED; + } + + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, + "ProxyProtocol: enabled on connection to %s:%hu", + c->local_ip, c->local_addr->port); + + /* this holds the resolved proxy info for this connection */ + conn_conf = apr_pcalloc(c->pool, sizeof(*conn_conf)); + ap_set_module_config(c->conn_config, &proxy_protocol_module, conn_conf); + + return OK; +} + +/* Set the request's useragent fields to our client info. + */ +static int pp_hook_post_read_request(request_rec *r) +{ + pp_conn_config *conn_conf; + + conn_conf = ap_get_module_config(r->connection->conn_config, + &proxy_protocol_module); + if (!conn_conf || !conn_conf->client_addr) { + return DECLINED; + } + + r->useragent_addr = conn_conf->client_addr; + r->useragent_ip = conn_conf->client_ip; + + return OK; +} + +typedef enum { HDR_DONE, HDR_ERROR, HDR_NEED_MORE } pp_parse_status_t; + +/* + * Human readable format: + * PROXY {TCP4|TCP6|UNKNOWN} + */ +static pp_parse_status_t pp_process_v1_header(conn_rec *c, + pp_conn_config *conn_conf, + proxy_header *hdr, apr_size_t len, + apr_size_t *hdr_len) +{ + char *end, *word, *host, *valid_addr_chars, *saveptr; + char buf[sizeof(hdr->v1.line)]; + apr_port_t port; + apr_status_t ret; + apr_int32_t family; + +#define GET_NEXT_WORD(field) \ + word = apr_strtok(NULL, " ", &saveptr); \ + if (!word) { \ + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, \ + "ProxyProtocol: no " field " found in header '%s'", \ + hdr->v1.line); \ + return HDR_ERROR; \ + } + + end = memchr(hdr->v1.line, '\r', len - 1); + if (!end || end[1] != '\n') { + return HDR_NEED_MORE; /* partial or invalid header */ + } + + *end = '\0'; + *hdr_len = end + 2 - hdr->v1.line; /* skip header + CRLF */ + + /* parse in separate buffer so have the original for error messages */ + strcpy(buf, hdr->v1.line); + + apr_strtok(buf, " ", &saveptr); + + /* parse family */ + GET_NEXT_WORD("family") + if (strcmp(word, "UNKNOWN") == 0) { + conn_conf->client_addr = c->client_addr; + conn_conf->client_ip = c->client_ip; + return HDR_DONE; + } + else if (strcmp(word, "TCP4") == 0) { + family = APR_INET; + valid_addr_chars = "0123456789."; + } + else if (strcmp(word, "TCP6") == 0) { + family = APR_INET6; + valid_addr_chars = "0123456789abcdefABCDEF:"; + } + else { + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, + "ProxyProtocol: unknown family '%s' in header '%s'", + word, hdr->v1.line); + return HDR_ERROR; + } + + /* parse client-addr */ + GET_NEXT_WORD("client-address") + + if (strspn(word, valid_addr_chars) != strlen(word)) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, + "ProxyProtocol: invalid client-address '%s' found in " + "header '%s'", word, hdr->v1.line); + return HDR_ERROR; + } + + host = word; + + /* parse dest-addr */ + GET_NEXT_WORD("destination-address") + + /* parse client-port */ + GET_NEXT_WORD("client-port") + if (sscanf(word, "%hu", &port) != 1) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, + "ProxyProtocol: error parsing port '%s' in header '%s'", + word, hdr->v1.line); + return HDR_ERROR; + } + + /* parse dest-port */ + /* GET_NEXT_WORD("destination-port") - no-op since we don't care about it */ + + /* create a socketaddr from the info */ + ret = apr_sockaddr_info_get(&conn_conf->client_addr, host, family, port, 0, + c->pool); + if (ret != APR_SUCCESS) { + conn_conf->client_addr = NULL; + ap_log_cerror(APLOG_MARK, APLOG_ERR, ret, c, + "ProxyProtocol: error converting family '%d', host '%s'," + " and port '%hu' to sockaddr; header was '%s'", + family, host, port, hdr->v1.line); + return HDR_ERROR; + } + + conn_conf->client_ip = apr_pstrdup(c->pool, host); + + return HDR_DONE; +} + +/* Binary format: + * + * sig = \x0D \x0A \x0D \x0A \x00 \x0D \x0A \x51 \x55 \x49 \x54 \x0A + * cmd = <4-bits-version><4-bits-command> + * 4-bits-version = \x02 + * 4-bits-command = {\x00|\x01} (\x00 = LOCAL: discard con info; \x01 = PROXY) + * proto = <4-bits-family><4-bits-protocol> + * 4-bits-family = {\x00|\x01|\x02|\x03} (AF_UNSPEC, AF_INET, AF_INET6, AF_UNIX) + * 4-bits-protocol = {\x00|\x01|\x02} (UNSPEC, STREAM, DGRAM) + */ +static pp_parse_status_t pp_process_v2_header(conn_rec *c, + pp_conn_config *conn_conf, + proxy_header *hdr) +{ + apr_status_t ret; + + switch (hdr->v2.ver_cmd & 0xF) { + case 0x01: /* PROXY command */ + switch (hdr->v2.fam) { + case 0x11: /* TCPv4 */ + ret = apr_sockaddr_info_get(&conn_conf->client_addr, NULL, + APR_INET, + ntohs(hdr->v2.addr.ip4.src_port), + 0, c->pool); + if (ret != APR_SUCCESS) { + conn_conf->client_addr = NULL; + ap_log_cerror(APLOG_MARK, APLOG_ERR, ret, c, + "ProxyProtocol: error creating sockaddr"); + return HDR_ERROR; + } + + conn_conf->client_addr->sa.sin.sin_addr.s_addr = + hdr->v2.addr.ip4.src_addr; + break; + + case 0x21: /* TCPv6 */ + ret = apr_sockaddr_info_get(&conn_conf->client_addr, NULL, + APR_INET6, + ntohs(hdr->v2.addr.ip6.src_port), + 0, c->pool); + if (ret != APR_SUCCESS) { + conn_conf->client_addr = NULL; + ap_log_cerror(APLOG_MARK, APLOG_ERR, ret, c, + "ProxyProtocol: error creating sockaddr"); + return HDR_ERROR; + } + + memcpy(&conn_conf->client_addr->sa.sin6.sin6_addr.s6_addr, + hdr->v2.addr.ip6.src_addr, 16); + break; + + default: + /* unsupported protocol */ + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(10183) + "RemoteIPProxyProtocol: unsupported protocol %.2hx", + (unsigned short)hdr->v2.fam); + return HDR_ERROR; + } + break; /* we got a sockaddr now */ + + default: + /* not a supported command */ + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, + "ProxyProtocol: unsupported command %.2hx", + hdr->v2.ver_cmd); + return HDR_ERROR; + } + + /* got address - compute the client_ip from it */ + ret = apr_sockaddr_ip_get(&conn_conf->client_ip, conn_conf->client_addr); + if (ret != APR_SUCCESS) { + conn_conf->client_addr = NULL; + ap_log_cerror(APLOG_MARK, APLOG_ERR, ret, c, + "ProxyProtocol: error converting address to string"); + return HDR_ERROR; + } + + return HDR_DONE; +} + +/* Determine if this is a v1 or v2 header. + */ +static int pp_determine_version(conn_rec *c, const char *ptr) +{ + proxy_header *hdr = (proxy_header *) ptr; + + /* assert len >= 14 */ + + if (memcmp(&hdr->v2, v2sig, sizeof(v2sig)) == 0 && + (hdr->v2.ver_cmd & 0xF0) == 0x20) { + return 2; + } + else if (memcmp(hdr->v1.line, "PROXY ", 6) == 0) { + return 1; + } + else { + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, + "ProxyProtocol: no valid header found"); + return -1; + } +} + +/* Capture the first bytes on the protocol and parse the proxy protocol header. + * Removes itself when the header is complete. + */ +static apr_status_t pp_input_filter(ap_filter_t *f, + apr_bucket_brigade *bb_out, + ap_input_mode_t mode, + apr_read_type_e block, + apr_off_t readbytes) +{ + apr_status_t ret; + pp_filter_context *ctx = f->ctx; + pp_conn_config *conn_conf; + apr_bucket *b; + pp_parse_status_t psts; + const char *ptr; + apr_size_t len; + + if (f->c->aborted) { + return APR_ECONNABORTED; + } + + /* allocate/retrieve the context that holds our header */ + if (!ctx) { + ctx = f->ctx = apr_palloc(f->c->pool, sizeof(*ctx)); + ctx->rcvd = 0; + ctx->need = MIN_HDR_LEN; + ctx->version = 0; + ctx->mode = AP_MODE_READBYTES; + ctx->bb = apr_brigade_create(f->c->pool, f->c->bucket_alloc); + ctx->done = 0; + } + + if (ctx->done) { + /* Note: because we're a connection filter we can't remove ourselves + * when we're done, so we have to stay in the chain and just go into + * passthrough mode. + */ + return ap_get_brigade(f->next, bb_out, mode, block, readbytes); + } + + conn_conf = ap_get_module_config(f->c->conn_config, &proxy_protocol_module); + + /* try to read a header's worth of data */ + while (!ctx->done) { + if (APR_BRIGADE_EMPTY(ctx->bb)) { + apr_off_t got, want = ctx->need - ctx->rcvd; + ret = ap_get_brigade(f->next, ctx->bb, ctx->mode, block, want); + if (ret != APR_SUCCESS) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, ret, f->c, APLOGNO(10184) + "failed reading input"); + return ret; + } + + ret = apr_brigade_length(ctx->bb, 1, &got); + if (ret || got > want) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, ret, f->c, APLOGNO(10185) + "RemoteIPProxyProtocol header too long, " + "got %" APR_OFF_T_FMT " expected %" APR_OFF_T_FMT, + got, want); + f->c->aborted = 1; + return APR_ECONNABORTED; + } + } + + if (APR_BRIGADE_EMPTY(ctx->bb)) { + return APR_EOF; + } + + while (!ctx->done && !APR_BRIGADE_EMPTY(ctx->bb)) { + b = APR_BRIGADE_FIRST(ctx->bb); + + ret = apr_bucket_read(b, &ptr, &len, block); + if (APR_STATUS_IS_EAGAIN(ret) && block == APR_NONBLOCK_READ) { + return APR_SUCCESS; + } + if (ret != APR_SUCCESS) { + return ret; + } + + memcpy(ctx->header + ctx->rcvd, ptr, len); + ctx->rcvd += len; + + apr_bucket_delete(b); + psts = HDR_NEED_MORE; + + if (ctx->version == 0) { + /* reading initial chunk */ + if (ctx->rcvd >= MIN_HDR_LEN) { + ctx->version = pp_determine_version(f->c, ctx->header); + if (ctx->version < 0) { + psts = HDR_ERROR; + } + else if (ctx->version == 1) { + ctx->mode = AP_MODE_GETLINE; + ctx->need = sizeof(proxy_v1); + } + else if (ctx->version == 2) { + ctx->need = MIN_V2_HDR_LEN; + } + } + } + else if (ctx->version == 1) { + psts = pp_process_v1_header(f->c, conn_conf, + (proxy_header *) ctx->header, + ctx->rcvd, &ctx->need); + } + else if (ctx->version == 2) { + proxy_header *hdr = (proxy_header *) ctx->header; + + if (ctx->rcvd >= MIN_V2_HDR_LEN) { + ctx->need = MIN_V2_HDR_LEN + ntohs(hdr->v2.len); + if (ctx->need > sizeof(proxy_v2)) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, f->c, APLOGNO(10186) + "RemoteIPProxyProtocol protocol header length too long"); + f->c->aborted = 1; + apr_brigade_destroy(ctx->bb); + return APR_ECONNABORTED; + } + } + if (ctx->rcvd >= ctx->need) { + psts = pp_process_v2_header(f->c, conn_conf, hdr); + } + } + else { + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, f->c, + "ProxyProtocol: internal error: unknown version " + "%d", ctx->version); + f->c->aborted = 1; + apr_brigade_destroy(ctx->bb); + return APR_ECONNABORTED; + } + + switch (psts) { + case HDR_ERROR: + f->c->aborted = 1; + apr_brigade_destroy(ctx->bb); + return APR_ECONNABORTED; + + case HDR_DONE: + ctx->done = 1; + break; + + case HDR_NEED_MORE: + break; + } + } + } + + /* we only get here when done == 1 */ + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, + "ProxyProtocol: received valid header: %s:%hu", + conn_conf->client_ip, conn_conf->client_addr->port); + + if (ctx->rcvd > ctx->need || !APR_BRIGADE_EMPTY(ctx->bb)) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, f->c, + "ProxyProtocol: internal error: have data left over; " + " need=%lu, rcvd=%lu, brigade-empty=%d", ctx->need, + ctx->rcvd, APR_BRIGADE_EMPTY(ctx->bb)); + f->c->aborted = 1; + apr_brigade_destroy(ctx->bb); + return APR_ECONNABORTED; + } + + /* clean up */ + apr_brigade_destroy(ctx->bb); + ctx->bb = NULL; + + /* now do the real read for the upper layer */ + return ap_get_brigade(f->next, bb_out, mode, block, readbytes); +} + +static void proxy_protocol_register_hooks(apr_pool_t *p) +{ + /* mod_ssl is CONNECTION + 5, so we want something higher (earlier); + * mod_reqtimeout is CONNECTION + 8, so we want something lower (later) */ + ap_register_input_filter(pp_inp_filter, pp_input_filter, NULL, + AP_FTYPE_CONNECTION + 7); + + ap_hook_pre_config(pp_hook_pre_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_post_config(pp_hook_post_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_pre_connection(pp_hook_pre_connection, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_post_read_request(pp_hook_post_read_request, NULL, NULL, + APR_HOOK_REALLY_FIRST); +} + +/* Dispatch list for API hooks */ +AP_DECLARE_MODULE(proxy_protocol) = { + STANDARD20_MODULE_STUFF, + NULL, /* create per-dir config structures */ + NULL, /* merge per-dir config structures */ + NULL, /* create per-server config structures */ + NULL, /* merge per-server config structures */ + proxy_protocol_cmds, /* table of config file commands */ + proxy_protocol_register_hooks /* register hooks */ +}; diff --git a/mod_proxy_protocol.xml b/mod_proxy_protocol.xml new file mode 100644 index 0000000..47ce51a --- /dev/null +++ b/mod_proxy_protocol.xml @@ -0,0 +1,126 @@ + + + + + + + + +mod_proxy_protocol +Implements the server side of the proxy protocol. +Extension +mod_proxy_protocol.c +proxy_protocol_module + + +

mod_proxy_protocol implements the server side of + HAProxy's + Proxy Protocol.

+ +

The module overrides the client IP address for the connection + with the information supplied by the upstream proxy in the proxy + protocol (connection) header.

+ +

This overridden useragent IP address is then used for the + mod_authz_host + Require ip + feature, is reported by mod_status, and is recorded by + mod_log_config %a and core + %a format strings. The underlying client IP of the connection + is available in the %{c}a format string.

+ + It is critical to only enable this behavior from + intermediate proxies which are trusted by this server, since it is trivial + for the remote client to impersonate another client. Currently this must + be done by external means (such as a firewall) as this module does not + (yet) implement access controls. +
+Proxy Protocol Spec + + +ProxyProtocol +Enable or disable the proxy protocol handling +ProxyProtocol On|Off +server configvirtual host + + + +

The ProxyProtocol enables or disables the + reading and handling of the proxy protocol connection header. If enabled + the upstream client must send the header every time it opens a + connection or the connection will get aborted.

+ +

While this directive may be specified in any virtual host, it is + important to understand that because the proxy protocol is connection + based and protocol agnostic, the enabling and disabling is actually based + on ip-address and port. This means that if you have multiple name-based + virtual hosts for the same host and port, and you enable it any one of + them, then it is enabled for all them (with that host and port). It also + means that if you attempt to enable the proxy protocol in one and disable + in the other, that won't work; in such a case the last one wins and a + notice will be logged indicating which setting was being overridden.

+ + + ProxyProtocol On + +
+
+ + + +
diff --git a/mod_proxy_protocol.xml.meta b/mod_proxy_protocol.xml.meta new file mode 100644 index 0000000..9f95c5f --- /dev/null +++ b/mod_proxy_protocol.xml.meta @@ -0,0 +1,7 @@ + + + + mod_proxy_protocol + /mod/ + https://httpd.apache.org/docs/2.4 +