Commit 5ddd8fe2 authored by Daniel Ruggeri's avatar Daniel Ruggeri
Browse files

Update PROXY handling by removing Optional processing

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1789800 13f79535-47bb-0310-9956-ffa450edef68
parent 267c77da
Loading
Loading
Loading
Loading
+30 −23
Original line number Diff line number Diff line
@@ -223,19 +223,19 @@ RemoteIPProxiesHeader X-Forwarded-By

<directivesynopsis>
<name>RemoteIPProxyProtocol</name>
<description>Enable, optionally enable or disable the PROXY protocol handling</description>
<syntax>RemoteIPProxyProtocol On|Optional|Off</syntax>
<description>Enable or disable PROXY protocol handling</description>
<syntax>RemoteIPProxyProtocol On|Off</syntax>
<contextlist><context>server config</context><context>virtual host</context>
</contextlist>
<compatibility>RemoteIPProxyProtocol is only available in httpd 2.4.26 and newer</compatibility>

<usage>
    <p>The <directive>RemoteIPProxyProtocol</directive> enables or 
    <p>The <directive>RemoteIPProxyProtocol</directive> directive enables or 
    disables the reading and handling of the PROXY protocol connection header.
    If enabled with the <code>On</code> flag, the upstream client <em>must</em>
    send the header every time it opens a connection or the connection will
    be aborted. If enabled with the <code>Optional</code> flag, the upstream
    client <em>may</em> send the header.</p>
    be aborted unless it is in the list of disabled hosts provided by <directive
    module="mod_remoteip">RemoteIPProxyProtocolDisableHosts</directive> directive.

    <p>While this directive may be specified in any virtual host, it is
    important to understand that because the PROXY protocol is connection
@@ -247,45 +247,52 @@ RemoteIPProxiesHeader X-Forwarded-By
    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.</p>

    <note type="hint">When multiple virtual hosts on the same IP and port are
    configured with a combination of <code>On</code> and <code>Optional</code>
    flags, connections will not be aborted if the header is not sent.
    Instead, enforcement will happen after the request is read so virtual
    hosts configured with <code>On</code> will return a 400 Bad Request.
    Virtual hosts configured with <code>Optional</code> will continue as
    usual but without replacing the client IP information</note>
    
    <highlight language="config">
Listen 80
&lt;VirtualHost *:80&gt;
    ServerName www.example.com
    RemoteIPProxyProtocol Optional

    #Requests to this virtual host may optionally not have
    # a PROXY protocol header provided
&lt;/VirtualHost&gt;

&lt;VirtualHost *:80&gt;
    ServerName www.example.com
    RemoteIPProxyProtocol On

    #Requests to this virtual host must have a PROXY protocol
    # header provided. If it is missing, a 400 will result
    # header provided. If it is missing, the connection will
    # be aborted
&lt;/VirtualHost&gt;

Listen 8080
&lt;VirtualHost *:8080&gt;
    ServerName www.example.com
    RemoteIPProxyProtocol On
    RemoteIPProxyProtocolDisableHosts 127.0.0.1 10.0.0.0/8

    #Requests to this virtual host must have a PROXY protocol
    # header provided. If it is missing, the connection will
    # be aborted
    # be aborted except when coming from localhost or the
    # 10.x.x.x RFC1918 range
&lt;/VirtualHost&gt;
    </highlight>
</usage>
</directivesynopsis>

<directivesynopsis>
<name>RemoteIPProxyProtocolDisableHosts</name>
<description>Disable processing of PROXY header for certain hosts or networks</description>
<syntax>RemoteIPProxyProtocolDisableHosts host|range [host|range] [host|range]</syntax>
<contextlist><context>server config</context><context>virtual host</context>
</contextlist>
<compatibility>RemoteIPProxyProtocolDisableHosts is only available in httpd 2.4.26 and newer</compatibility>

<usage>
    <p>The <directive>RemoteIPProxyProtocol</directive> directive enables or
    disables the reading and handling of the PROXY protocol connection header.
    Sometimes it is desirable to require clients to provide the PROXY header, but
    permit other clients to connect without it. This directive allows a server 
    administrator to configure a single host or CIDR range of hosts that may do
    so. This is generally useful for useful for monitoring and administrative
    traffic to a virtual host direct to the server behind the upstream load 
    balancer.</p>
</usage>
</directivesynopsis>

<directivesynopsis>
<name>RemoteIPTrustedProxy</name>
<description>Restrict client IP addresses trusted to present the RemoteIPHeader value</description>
+98 −174
Original line number Diff line number Diff line
@@ -61,14 +61,9 @@ typedef struct {
    apr_array_header_t *proxymatch_ip;

    remoteip_addr_info *proxy_protocol_enabled;
    remoteip_addr_info *proxy_protocol_optional;
    remoteip_addr_info *proxy_protocol_disabled;

    /** A flag indicating whether or not proxyprotocol
     * is optoinal for this specific server
     */
    int pp_optional;

    apr_array_header_t *disabled_subnets;
    apr_pool_t *pool;
} remoteip_config_t;

@@ -150,22 +145,18 @@ typedef struct {
    apr_sockaddr_t *client_addr;
    /** Character representation of the client */
    char *client_ip;
    /** Flag indicating that the PROXY header may be omitted on this
      connection (do not abort if it is missing). */
    int proxy_protocol_optional;
} remoteip_conn_config_t;

typedef enum { HDR_DONE, HDR_ERROR, HDR_MISSING, HDR_NEED_MORE } remoteip_parse_status_t;
typedef enum { HDR_DONE, HDR_ERROR, HDR_NEED_MORE } remoteip_parse_status_t;

static void *create_remoteip_server_config(apr_pool_t *p, server_rec *s)
{
    remoteip_config_t *config = apr_pcalloc(p, sizeof *config);
    remoteip_config_t *config = apr_pcalloc(p, sizeof(*config));
    config->disabled_subnets = apr_array_make(p, 1, sizeof(apr_ipsubnet_t *));
    /* config->header_name = NULL;
     * config->proxies_header_name = NULL;
     * config->proxy_protocol_enabled = NULL;
     * config->proxy_protocol_optional = NULL;
     * config->proxy_protocol_disabled = NULL;
     * config->pp_optional = 0;
     */
    config->pool = p;
    return config;
@@ -188,9 +179,6 @@ static void *merge_remoteip_server_config(apr_pool_t *p, void *globalv,
    config->proxymatch_ip = server->proxymatch_ip
                          ? server->proxymatch_ip
                          : global->proxymatch_ip;
    config->pp_optional = server->pp_optional
                          ? server->pp_optional
                          : global->pp_optional;
    return config;
}

@@ -363,7 +351,7 @@ static int remoteip_addr_in_list(remoteip_addr_info *list, apr_sockaddr_t *addr)
    return 0;
}

static void remoteip_warn_enable_conflict(remoteip_addr_info *prev, server_rec *new, const char* arg)
static void remoteip_warn_enable_conflict(remoteip_addr_info *prev, server_rec *new, int flag)
{
    char buf[INET6_ADDRSTRLEN];

@@ -376,70 +364,51 @@ static void remoteip_warn_enable_conflict(remoteip_addr_info *prev, server_rec *
                 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,
                 arg);
                 flag ? "On" : "Off");
}

static const char *remoteip_enable_proxy_protocol(cmd_parms *cmd, void *config,
                                                  const char *arg)
                                                  int flag)
{
    remoteip_config_t *global_conf;
    remoteip_config_t *server_conf;
    remoteip_config_t *conf;
    server_addr_rec *addr;
    remoteip_addr_info **add;
    int list_len = 2;
    remoteip_addr_info **rem_list[list_len];
    remoteip_addr_info **rem;
    remoteip_addr_info *list;
    int i;

    global_conf = ap_get_module_config(ap_server_conf->module_config,
                                &remoteip_module);
    server_conf = ap_get_module_config(cmd->server->module_config,
    conf = ap_get_module_config(ap_server_conf->module_config,
                                &remoteip_module);

    if (strcasecmp(arg, "On") == 0) {
        add = &global_conf->proxy_protocol_enabled;
        rem_list[0] = &global_conf->proxy_protocol_optional;
        rem_list[1] = &global_conf->proxy_protocol_disabled;
    }
    else if (strcasecmp(arg, "Optional") == 0) {
        add = &global_conf->proxy_protocol_optional;
        rem_list[0] = &global_conf->proxy_protocol_enabled;
        rem_list[1] = &global_conf->proxy_protocol_disabled;
        server_conf->pp_optional = 1;
    }
    else if (strcasecmp(arg, "Off") == 0 ) {
        add = &global_conf->proxy_protocol_disabled;
        rem_list[0] = &global_conf->proxy_protocol_enabled;
        rem_list[1] = &global_conf->proxy_protocol_optional;
    if (flag) {
        add = &conf->proxy_protocol_enabled;
        rem = &conf->proxy_protocol_disabled;
    }
    else {
        return apr_pstrcat(cmd->pool, "Unrecognized option for %s `%s'", cmd->cmd->name, arg, NULL);
        add = &conf->proxy_protocol_disabled;
        rem = &conf->proxy_protocol_enabled;
    }

    for (addr = cmd->server->addrs; addr; addr = addr->next) {
        /* remove address from other lists */
        for (i = 0; i < list_len ; i++) { 
            remoteip_addr_info **rem = rem_list[i];
        /* remove address from opposite list */
        if (*rem) {
            if (remoteip_sockaddr_equal((*rem)->addr, addr->host_addr)) {
                    remoteip_warn_enable_conflict(*rem, cmd->server, arg);
                remoteip_warn_enable_conflict(*rem, cmd->server, flag);
                *rem = (*rem)->next;
            }
            else {
                for (list = *rem; list->next; list = list->next) {
                    if (remoteip_sockaddr_equal(list->next->addr, addr->host_addr)) {
                            remoteip_warn_enable_conflict(list->next, cmd->server, arg);
                        remoteip_warn_enable_conflict(list->next, cmd->server, flag);
                        list->next = list->next->next;
                        break;
                    }
                }
            }
        }
        }

        /* add address to desired list */
        if (!remoteip_addr_in_list(*add, addr->host_addr)) {
            remoteip_addr_info *info = apr_palloc(global_conf->pool, sizeof(*info));
            remoteip_addr_info *info = apr_palloc(conf->pool, sizeof(*info));
            info->addr = addr->host_addr;
            info->source = cmd->server;
            info->next = *add;
@@ -450,6 +419,45 @@ static const char *remoteip_enable_proxy_protocol(cmd_parms *cmd, void *config,
    return NULL;
}

static const char *remoteip_disable_networks(cmd_parms *cmd, void *d,
                                             int argc, char *const argv[])
{
    int i;
    apr_pool_t *ptemp = cmd->temp_pool;
    apr_pool_t *p = cmd->pool;
    remoteip_config_t *conf = ap_get_module_config(ap_server_conf->module_config,
                                &remoteip_module);

    if (argc == 0)
        return "RemoteIPProxyProtocolDisableNetworks requires an argument";


    for (i=0; i<argc; i++) {
        char *addr = apr_pstrdup(ptemp, argv[i]);
        char *mask;
        apr_status_t rv;
        apr_ipsubnet_t **ip = apr_pcalloc(p, sizeof(apr_ipsubnet_t *));

        if ((mask = ap_strchr(addr, '/')))
            *mask++ = '\0';

        rv = apr_ipsubnet_create(ip, addr, mask, p);

        if(APR_STATUS_IS_EINVAL(rv)) {
            /* looked nothing like an IP address */
            return apr_psprintf(p, "ip address '%s' appears to be invalid", addr);
        }
        else if (rv != APR_SUCCESS) {
            return apr_psprintf(p, "ip address '%s' appears to be invalid: %pm",
                                addr, &rv);
        }

        *(apr_ipsubnet_t**)apr_array_push(conf->disabled_subnets) = *ip;
    }

    return NULL;
}

static int remoteip_hook_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
                              apr_pool_t *ptemp)
{
@@ -476,11 +484,6 @@ static int remoteip_hook_post_config(apr_pool_t *pconf, apr_pool_t *plog,
        ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, APLOGNO(03492)
                     "RemoteIPProxyProtocol: enabled on %s:%hu", buf, info->addr->port);
    }
    for (info = conf->proxy_protocol_optional; info; info = info->next) {
        apr_sockaddr_ip_getbuf(buf, sizeof(buf), info->addr);
        ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, APLOGNO(03493)
                     "RemoteIPProxyProtocol: optional on %s:%hu", buf, info->addr->port);
    }
    for (info = conf->proxy_protocol_disabled; info; info = info->next) {
        apr_sockaddr_ip_getbuf(buf, sizeof(buf), info->addr);
        ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, APLOGNO(03494)
@@ -523,19 +526,11 @@ static int remoteip_modify_request(request_rec *r)
       protocol handling allowing it to take precedence and return
    */
    if (conn_config) {
        /* We may have gotten here if processing was optional - check for that */
        if (!conn_config->client_addr) {
            if (config->pp_optional) {
                ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03495)
                              "RemoteIPProxyProtocol data is missing, but was optional. Allowing request.");
                return OK;
            }
            else {
            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(03496)
                          "RemoteIPProxyProtocol data is missing, but required! Aborting request.");
            return HTTP_BAD_REQUEST;
        }
        }

        r->useragent_addr = conn_config->client_addr;
        r->useragent_ip = conn_config->client_ip;
@@ -860,7 +855,7 @@ static int remoteip_hook_pre_connection(conn_rec *c, void *csd)
{
    remoteip_config_t *conf;
    remoteip_conn_config_t *conn_conf;
    int optional;
    int i;

    /* Do not attempt to manipulate slave connections */
    if (c->master != NULL) {
@@ -870,13 +865,19 @@ static int remoteip_hook_pre_connection(conn_rec *c, void *csd)
    conf = ap_get_module_config(ap_server_conf->module_config,
                                &remoteip_module);

    /* Used twice - do the check only once */
    optional = remoteip_addr_in_list(conf->proxy_protocol_optional, c->local_addr);

    /* check if we're enabled for this connection */
    if ((!remoteip_addr_in_list(conf->proxy_protocol_enabled, c->local_addr)
          && !optional )
    if (!remoteip_addr_in_list(conf->proxy_protocol_enabled, c->local_addr)
        || remoteip_addr_in_list(conf->proxy_protocol_disabled, c->local_addr)) {

        return DECLINED;
    }

    /* We are enabled for this IP/port, but check that we aren't
       explicitly disabled */
    for (i = 0; i < conf->disabled_subnets->nelts; i++) {
        apr_ipsubnet_t *ip = ((apr_ipsubnet_t**)conf->disabled_subnets->elts)[i];

        if (ip && apr_ipsubnet_test(ip, c->client_addr))
            return DECLINED;
    }

@@ -898,12 +899,6 @@ static int remoteip_hook_pre_connection(conn_rec *c, void *csd)
    /* this holds the resolved proxy info for this connection */
    conn_conf = apr_pcalloc(c->pool, sizeof(*conn_conf));

    /* Propagate the optional flag so the connection handler knows not to
       abort if the header is mising. NOTE: This means we must check after
       we read the request that the header was NOT optional, too.
    */
    conn_conf->proxy_protocol_optional = optional;

    ap_set_module_config(c->conn_config, &remoteip_module, conn_conf);

    return OK;
@@ -1036,31 +1031,20 @@ static apr_status_t remoteip_input_filter(ap_filter_t *f,
    remoteip_parse_status_t psts = HDR_NEED_MORE;
    const char *ptr;
    apr_size_t len;
    apr_size_t this_read = 0; /* Track bytes read in each brigade */
    apr_size_t prev_read = 0;

    if (f->c->aborted) {
        return APR_ECONNABORTED;
    }

    conn_conf = ap_get_module_config(f->c->conn_config, &remoteip_module);

    /* 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 (conn_conf->proxy_protocol_optional) {
            ctx->mode = AP_MODE_SPECULATIVE;
            ctx->peeking = 1;
        }
        else {
            ctx->mode = AP_MODE_READBYTES;
            ctx->peeking = 0;
        }
    }

    if (ctx->done) {
@@ -1071,6 +1055,8 @@ static apr_status_t remoteip_input_filter(ap_filter_t *f,
        return ap_get_brigade(f->next, bb_out, mode, block, readbytes);
    }

    conn_conf = ap_get_module_config(f->c->conn_config, &remoteip_module);

    /* try to read a header's worth of data */
    while (!ctx->done) {
        if (APR_BRIGADE_EMPTY(ctx->bb)) {
@@ -1084,23 +1070,9 @@ static apr_status_t remoteip_input_filter(ap_filter_t *f,
            return block == APR_NONBLOCK_READ ? APR_SUCCESS : APR_EOF;
        }

        if (ctx->peeking) {
            ctx->rcvd = 0;
            ctx->need = MIN_HDR_LEN;
        }

        while (!ctx->done && !APR_BRIGADE_EMPTY(ctx->bb)) {
            b = APR_BRIGADE_FIRST(ctx->bb);

            if (ctx->peeking && APR_BUCKET_IS_EOS(b)) {
                /* Shortcut - we know no header was found yet and an
                   EOS indicates we never will */
                apr_brigade_destroy(ctx->bb);
                ctx->bb = NULL;
                ctx->done = 1;
                return APR_SUCCESS;
            }

            ret = apr_bucket_read(b, &ptr, &len, block);
            if (APR_STATUS_IS_EAGAIN(ret) && block == APR_NONBLOCK_READ) {
                return APR_SUCCESS;
@@ -1112,10 +1084,6 @@ static apr_status_t remoteip_input_filter(ap_filter_t *f,
            memcpy(ctx->header + ctx->rcvd, ptr, len);
            ctx->rcvd += len;

            if (ctx->peeking && block == APR_NONBLOCK_READ) {
                this_read += len;
            }

            apr_bucket_delete(b);
            psts = HDR_NEED_MORE;

@@ -1123,37 +1091,8 @@ static apr_status_t remoteip_input_filter(ap_filter_t *f,
                /* reading initial chunk */
                if (ctx->rcvd >= MIN_HDR_LEN) {
                    ctx->version = remoteip_determine_version(f->c, ctx->header);

                    /* We've read enough to know that a header is present. In peek mode
                       we purge the bb and can decide to step aside or switch to
                       non-speculative read to consume the data */
                    if (ctx->peeking) {
                    if (ctx->version < 0) {
                            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, APLOGNO(03512)
                                          "RemoteIPProxyProtocol: PROXY header is missing from "
                                          "request. Stepping aside.");
                            apr_brigade_destroy(ctx->bb);
                            ctx->bb = NULL;
                            ctx->done = 1;
                            return ap_get_brigade(f->next, bb_out, mode, block, readbytes);
                        }
                        else {
                            /* Rest ctx to initial values */
                            ctx->rcvd = 0;
                            ctx->need = MIN_HDR_LEN;
                            ctx->version = 0;
                            ctx->done = 0;
                            ctx->mode = AP_MODE_READBYTES;
                            ctx->peeking = 0;
                            apr_brigade_cleanup(ctx->bb);

                            ap_get_brigade(f->next, ctx->bb, ctx->mode, block,
                                           ctx->need - ctx->rcvd);
                        }
                    }
                    else {
                        if (ctx->version < 0) {
                            psts = HDR_MISSING;
                        psts = HDR_ERROR;
                    }
                    else if (ctx->version == 1) {
                        ctx->mode = AP_MODE_GETLINE;
@@ -1164,7 +1103,6 @@ static apr_status_t remoteip_input_filter(ap_filter_t *f,
                    }
                }
            }
            }
            else if (ctx->version == 1) {
                psts = remoteip_process_v1_header(f->c, conn_conf,
                                            (proxy_header *) ctx->header,
@@ -1174,7 +1112,6 @@ static apr_status_t remoteip_input_filter(ap_filter_t *f,
                if (ctx->rcvd >= MIN_V2_HDR_LEN) {
                    ctx->need = MIN_V2_HDR_LEN +
                        remoteip_get_v2_len((proxy_header *) ctx->header);

                }
                if (ctx->rcvd >= ctx->need) {
                    psts = remoteip_process_v2_header(f->c, conn_conf,
@@ -1191,10 +1128,6 @@ static apr_status_t remoteip_input_filter(ap_filter_t *f,
            }

            switch (psts) {
                case HDR_MISSING:
                    ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, f->c, APLOGNO(03510)
                                 "RemoteIPProxyProtocol: no valid PROXY header found");
                        /* fall through to error case */
                case HDR_ERROR:
                    f->c->aborted = 1;
                    apr_brigade_destroy(ctx->bb);
@@ -1208,18 +1141,6 @@ static apr_status_t remoteip_input_filter(ap_filter_t *f,
                    break;
            }
        }

        /* In SPECULATIVE mode, upstream will return all data on each brigade get - even data
           we've seen.  For non blocking read, make sure we got new data or return early when
           we haven't */
        if (ctx->peeking && block == APR_NONBLOCK_READ) {
            if (this_read == prev_read) {
                return APR_SUCCESS;
            }
            else {
                prev_read = this_read;
            }
        }
    }

    /* we only get here when done == 1 */
@@ -1268,8 +1189,11 @@ static const command_rec remoteip_cmds[] =
                  RSRC_CONF | EXEC_ON_READ,
                  "The filename to read the list of internal proxies, "
                  "see the RemoteIPInternalProxy directive"),
    AP_INIT_TAKE1("RemoteIPProxyProtocol", remoteip_enable_proxy_protocol, NULL,
                  RSRC_CONF, "Enable PROXY protocol handling (`on', `off', `optional')"),
    AP_INIT_FLAG("RemoteIPProxyProtocol", remoteip_enable_proxy_protocol, NULL,
                  RSRC_CONF, "Enable PROXY protocol handling (`on', `off')"),
    AP_INIT_TAKE_ARGV("RemoteIPProxyProtocolDisableHosts",
                  remoteip_disable_networks, NULL, RSRC_CONF, "Disable PROXY "
                  "protocol handling for this list of networks in CIDR format"),
    { NULL }
};