Yann Ylavic wrote:
> On Mon, Feb 24, 2014 at 10:05 AM, Ruediger Pluem <[email protected]> wrote:
>>
>> Yann Ylavic wrote:
>>> There seem to be different questions in this thread regarding SNI.
>>> Maybe we can enumerate them first to see what's going on (at least I need
>>> to)
>>>
>>> 1. What should the client-provided SNI be checked against?
>>> 1.1. for server or proxy-reverse
>>> 1.2. for proxy-forward/CONNECT
>>>
>>> Possibilities are :
>>> 1.a. Host: header
>>> 1.b. Request-URI's hostname
>>> 1.c. ServerName/ServerAlias(es)
>>> 1.d. ap_get_server_name_for_url()
>>> 1.e. none
>>>
>>> 2. What should the proxy-provided SNI be?
>>> 2.1. when ProxyPreserveHost is off
>>> 2.2. when ProxyPreserveHost is on
>>>
>>> Possibilities are :
>>> 2.a. proxy worker's hostname
>>> 2.b. r->hostname
>>> 2.c. Host: header (being sent)
>>>
>>> 3. What should the backend's CN be checked against?
>>>
>>> 4. How should the proxy reuse SNI connections?
>>>
>>> Currently :
>>> 1.1 => 1.b if any, otherwise 1.a
>>> 1.2 => 1.e
>>> 2.1 => 2.a
>>> 2.2 => 2.b
>>> 3 => the proxy-provided SNI (or wildcard match)
>>> 4 => always
>>>
>>>
>>> I propose to use the following instead :
>>>
>>> 1.1 => 1.d
>>> So that the admin can configure UseCanonicalName to branch 1.b-a or 1.c.
>>
>> Host header and SNI should be consistent IMHO. So I think the current setup
>> is correct.
>
> I tend to agree but, as you can see, people have existing/working
> configurations where they don't use vhosting by SNI (backend side),
> and/or authenticate the backend based on the requested Host (proxy
> side): they use SSL at connection level only.
Even if they use IP/Port based virtual hosting the SNI name and supplied host
header should be consistent.
Maybe we can add a "don't use it" directive here that turns off this check. But
I am not in favor of this.
IMHO it is a bug of the client if both things are not consistent.
>
> By checking (client side) and setting (proxy side) the SNI
> unconditionally (based on the given Host), it's almost as if we forbid
> access to the default vhost (on some port) when the requested Host
> does not match any of its ServerName/ServerAlias(es).
I don't see that this is the case from the existing code. IMHO we compare two
different data pieces the
client supplied for consistency.
> AFAICT, httpd won't block anything like that by its own, that's what I
> meant by "hot potato" and "should be denied by httpd/mod_proxy before
> forwarding" (below).
>
> Why would it be different in the SSL+SNI case if the admin knows what
> (s)he does and can't use anything than the requested Host, the
> ServerName or the worker hostname?
>
>>
>
>>
>>>
>>> 2.1 => 2.c
>>> 2.2 => 2.c
>>> Both should use the Host: header since this is what the backend will check.
>>> Either the Host: is legitimate and we can forward it, or it should be
>>> denied by the proxy before forwarding (that's kind of hot potato sent
>>> to the backend otherwise).
>>
>> I strongly disagree. In the 2.1 case we need to set the host header and SNI
>> (provided no IP) as requested
>> by the admin. This configuration is usually on purpose and takes into
>> account that the internal servers have
>> different DNS names and a matching certificate / virtual host setup.
>> Regarding 2.2 I think that 2.b is still the correct choice as we use a
>> sanitized and normalized version of the
>> host header here.
>
> Sorry I mispoke here.
> I didn't mean to use the user-agent's requested Host as SNI in both
> cases, but using the same Host as the one set by
> ap_proxy_create_hdrbrgd() (the one that will finally reach the
> backend), ie :
>
> if (dconf->preserve_host == 0) {
> if (ap_strchr_c(uri->hostname, ':')) { /* if literal IPv6 address */
> if (uri->port_str && uri->port != DEFAULT_HTTP_PORT) {
> buf = apr_pstrcat(p, "Host: [", uri->hostname, "]:",
> uri->port_str, CRLF, NULL);
> } else {
> buf = apr_pstrcat(p, "Host: [", uri->hostname, "]", CRLF,
> NULL);
> }
> } else {
> if (uri->port_str && uri->port != DEFAULT_HTTP_PORT) {
> buf = apr_pstrcat(p, "Host: ", uri->hostname, ":",
> uri->port_str, CRLF, NULL);
> } else {
> buf = apr_pstrcat(p, "Host: ", uri->hostname, CRLF, NULL);
> }
> }
> }
> else {
> /* don't want to use r->hostname, as the incoming header might have a
> * port attached
> */
> const char* hostname = apr_table_get(r->headers_in,"Host");
> if (!hostname) {
> hostname = r->server->server_hostname;
> ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01092)
> "no HTTP 0.9 request (with no host line) "
> "on incoming request and preserve host set "
> "forcing hostname to be %s for uri %s",
> hostname, r->uri);
> }
> buf = apr_pstrcat(p, "Host: ", hostname, CRLF, NULL);
> }
> ap_xlate_proto_to_ascii(buf, strlen(buf));
> e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc);
> APR_BRIGADE_INSERT_TAIL(header_brigade, e);
>
> The code above and the one from proxy_http_handler() below (which sets
> the SNI via "proxy-request-hostname") are not consistent in all cases
It becomes consistent as
- We do not put a port in our SNI request as neither uri->hostname nor
r->hostname have one.
- We do only compare stuff without ports below.
> :
>
> if (is_ssl) {
> proxy_dir_conf *dconf;
> const char *ssl_hostname;
>
> /*
> * In the case of ProxyPreserveHost on use the hostname of
> * the request if present otherwise use the one from the
> * backend request URI.
> */
> dconf = ap_get_module_config(r->per_dir_config,
> &proxy_module);
> if ((dconf->preserve_host != 0) && (r->hostname != NULL)) {
> ssl_hostname = r->hostname;
> }
> else {
> ssl_hostname = uri->hostname;
> }
>
> apr_table_set(backend->connection->notes,
> "proxy-request-hostname",
> ssl_hostname);
> }
>
> Also, for proxied subrequests, r->hostname is not always "up to date".
> What I mean is that there should be one forwarded Host value (say the
Can you give an example?
> "proxy-request-hostname" note), computed once (according to
> ProxyPreserveHost on/off/canon?), and used everywhere (header, SNI,
> CheckPeerCN/Name...).
>
>>
>>
>>>
>>> 4 => compare reusable connection's hostname with 2.c (when conn->is_ssl
>>> only)
>>
>> This could be a worthwhile idea and I have seen your patch in bugzilla.
>> For ease of commenting I would like you to post it here such that inline
>> comments could
>> be made (I would have some).
>
> Here it is (thanks for looking into this).
>
> Index: modules/proxy/mod_proxy.h
> ===================================================================
> --- modules/proxy/mod_proxy.h (revision 1569048)
> +++ modules/proxy/mod_proxy.h (working copy)
> @@ -242,6 +242,7 @@ typedef struct {
> apr_pool_t *pool; /* Subpool for hostname and addr data */
> const char *uds_path; /* Unix domain socket path */
> const char *hostname;
> + const char *ssl_hostname;
As already pointed by Bill: Put it at the end of the struct to keep it
backportable (only minor bump required in this case).
> apr_sockaddr_t *addr; /* Preparsed remote address info */
> apr_pool_t *scpool; /* Subpool used for socket and
> connection data */
> apr_socket_t *sock; /* Connection socket */
> Index: modules/proxy/mod_proxy_http.c
> ===================================================================
> --- modules/proxy/mod_proxy_http.c (revision 1569048)
> +++ modules/proxy/mod_proxy_http.c (working copy)
> @@ -1916,6 +1916,7 @@ static int proxy_http_handler(request_rec *r, prox
> ap_proxy_ssl_connection_cleanup(backend, r);
> }
>
> +
> /*
> * In the case that we are handling a reverse proxy connection and this
> * is not a request that is coming over an already kept alive connection
> @@ -1958,25 +1959,10 @@ static int proxy_http_handler(request_rec *r, prox
> * requested, such that mod_ssl can check if it is requested to
> do
> * so.
> */
> - if (is_ssl) {
> - proxy_dir_conf *dconf;
> - const char *ssl_hostname;
> -
> - /*
> - * In the case of ProxyPreserveHost on use the hostname of
> - * the request if present otherwise use the one from the
> - * backend request URI.
> - */
> - dconf = ap_get_module_config(r->per_dir_config,
> &proxy_module);
> - if ((dconf->preserve_host != 0) && (r->hostname != NULL)) {
> - ssl_hostname = r->hostname;
> - }
> - else {
> - ssl_hostname = uri->hostname;
> - }
> -
> - apr_table_set(backend->connection->notes,
> "proxy-request-hostname",
> - ssl_hostname);
> + if (backend->ssl_hostname) {
> + apr_table_setn(backend->connection->notes,
> + "proxy-request-hostname",
> + backend->ssl_hostname);
> }
>
> /* Step Three-and-a-Half: See if the socket is still connected
> (if
> Index: modules/proxy/proxy_util.c
> ===================================================================
> --- modules/proxy/proxy_util.c (revision 1569048)
> +++ modules/proxy/proxy_util.c (working copy)
> @@ -2284,6 +2285,7 @@ ap_proxy_determine_connection(apr_pool_t *p, reque
> conn->pool);
> }
> socket_cleanup(conn);
> + conn->close = 0;
This one makes sense, but is not related to the contents of this patch. Please
commit it separately.
> }
> if (will_reuse) {
> /*
> @@ -2345,6 +2347,35 @@ ap_proxy_determine_connection(apr_pool_t *p, reque
> return ap_proxyerror(r, HTTP_FORBIDDEN,
> "Connect to remote machine blocked");
> }
> + /*
> + * When SSL is configured, determine the SNI for the request and
> + * save it in conn->ssl_hostname. Close any reused connection whose
> + * SNI differs.
> + */
> + if (conn->is_ssl) {
> + proxy_dir_conf *dconf;
> + const char *ssl_hostname;
> + /*
> + * In the case of ProxyPreserveHost on use the hostname of
> + * the request if present otherwise use the one from the
> + * backend request URI.
> + */
> + dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
> + if (dconf->preserve_host) {
> + ssl_hostname = r->hostname;
> + }
> + else {
> + ssl_hostname = conn->hostname;
> + }
> + if (conn->ssl_hostname != NULL &&
> + (!ssl_hostname || strcasecmp(conn->ssl_hostname,
> + ssl_hostname) != 0)) {
> + socket_cleanup(conn);
Shouldn't we set conn->close = 0 here as well?
> + }
> + if (conn->ssl_hostname == NULL) {
> + conn->ssl_hostname = apr_pstrdup(conn->scpool, ssl_hostname);
> + }
> + }
> ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00947)
> "connected %s to %s:%d", *url, conn->hostname, conn->port);
> return OK;
>
Regards
Rüdiger