Opened 5 months ago

Closed 5 months ago

Last modified 3 months ago

#1817 closed defect (wontfix)

HTTP2 Server Push doesn't include Authorization header into the push request made from Link: preload header

Reported by: dkarlovi@… Owned by:
Priority: minor Milestone:
Component: other Version: 1.17.x
Keywords: http2 server push Cc:
uname -a: Linux 756d801e34b7 5.1.16-300.fc30.x86_64 #1 SMP Wed Jul 3 15:06:51 UTC 2019 x86_64 Linux
nginx -V: Docker image nginx:1.17.1-alpine nginx version: nginx/1.17.1 built by gcc 8.2.0 (Alpine 8.2.0) built with OpenSSL 1.1.1b 26 Feb 2019 TLS SNI support enabled configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --with-perl_modules_path=/usr/lib/perl5/vendor_perl --user=nginx --group=nginx --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt='-Os -fomit-frame-pointer' --with-ld-opt=-Wl,--as-needed

Description

I have Nginx setup as a reverse proxy in front of my Varnish-enabled API app. API is using OAuth2 auth, requiring a Authorization header.

Relevant config:

server {
    listen 443 ssl http2 default_server;
    listen [::]:443 ssl http2 default_server;

    server_name _;

    location / {
        proxy_pass http://proxy/;
        http2_push_preload on;
    }
}

If I try to access an endpoint of my app, it will include the expected HTTP2 Server Push header:

 nghttp https://app.local/api/images/ee4241d8-aa01-11e9-b3e4-0242ac1f000b   -H 'Accept: application/json'   -H 'Accept-Language: hr'   -H 'Authorization: Bearer Zjc1MzQzM2ZlN2IzZWIxOTY1ZDE2YjlkZjI3NjU0MmMxYWVhNmRhMWI1ZjExODRiMDVkNGU4YmM0MGU5NmRjMg' -v

...
[  0.034] recv (stream_id=13) link: </api/images/f112c18a-aa01-11e9-bb11-0242ac1f000c/metadata>; rel="preload"
...

Nginx will subsequently create the second request, but this one will return "OAuth2 authentication required", because the Authorization header is mission on the subsequent (Nginx-triggered) request:

[  0.311] recv (stream_id=2) www-authenticate: Bearer realm="Service", error="access_denied", error_description="OAuth2 authentication required"
{"error":"access_denied","error_description":"OAuth2 authentication required"}
[  0.311] recv DATA frame <length=78, flags=0x01, stream_id=2>
          ; END_STREAM

Looking at Varnish debug log, the headers Nginx sends seem to be:

-   ReqMethod      GET
-   ReqURL         /api/images/f112c18a-aa01-11e9-bb11-0242ac1f000c/metadata
-   ReqProtocol    HTTP/1.0
-   ReqHeader      Host: proxy
-   ReqHeader      Connection: close
-   ReqHeader      accept-encoding: gzip, deflate
-   ReqHeader      accept-language: hr
-   ReqHeader      user-agent: nghttp2/1.38.0
-   ReqHeader      X-Forwarded-For: 172.31.0.13

Since the Authorization header is not present, Varnish obviously cannot pass it further and the application is correct to fail.

The header looks like it should be included. I'm pretty sure Accept header is also missing, but cannot be sure since it might get eaten by Varnish.

Change History (4)

comment:1 Changed 5 months ago by dkarlovi@…

Accept header is also definitely missing.

comment:2 Changed 5 months ago by ru

  • Resolution set to wontfix
  • Status changed from new to closed

Regarding the "Accept" header, please see this discussion: http://mailman.nginx.org/pipermail/nginx-devel/2018-February/010809.html

Regarding the "Authorization" header, the pushed resource can easily have a different realm, so we can't assume that the same credentials apply.

This problem has been discussed in the HTTP WG before HTTP/2 was ratified as the standard:
https://lists.w3.org/Archives/Public/ietf-http-wg/2015JanMar/0039.html
https://lists.w3.org/Archives/Public/ietf-http-wg/2015JanMar/0041.html
https://lists.w3.org/Archives/Public/ietf-http-wg/2015JanMar/0087.html

Generally, it's a bad idea to push resources that imply negotiation with the client.

comment:3 Changed 5 months ago by dkarlovi@…

Hi,

I understand the motivation behind obscuring these headers since they pose a compatibility / security risk.

However, this does mean Nginx is not capable of proxying modern REST-based APIs based on server push:

With HTTP/2 gaining more widespread support and these kinds of integrations already available to developers, leveraging Server Push for APIs seems like a great fit, while taking advantage of existing HTTP infrastructure and is likely to come in widespread use in the future.

What do you think about a feature which would allow the configuration to whitelist specific headers from incoming request into the preload request?

Example:

server {
    location / {
        proxy_pass http://proxy/;
        http2_push_preload on;
    }

    location /api {
        # list of headers to copy from incoming request to preload request
        http2_push_preload_headers Authentication Accept Accept-Language;
    }

    # alternatively
    location /api {
        # list of headers to add to preload request

        # copy from incoming request
        http2_push_preload_header Authentication $http_authentication;
        http2_push_preload_header Accept $http_accept;
        http2_push_preload_header Accept-Language $http_accept_language;

        # fully custom request
        http2_push_preload_header X-Custom-Header yes;
    }
}

This would give developers the tools to still handle this use case, while having the burden of everything being secure (not leaking access tokens and cookies to 3rd party realms) and compatible (origin servers expect headers as-is).

Last edited 5 months ago by dkarlovi@… (previous) (diff)

comment:4 Changed 3 months ago by mdounin

See also #1851.

Note: See TracTickets for help on using tickets.