vozlt/nginx-module-vts

bug: absolute URL prevent the use of a reverse proxy with a different path in front of the stats

netchild opened this issue · 4 comments

Hi,

I want to protect access to the vts stats. So at the official URL (let's assume "/vts") nginx proxies to an oauth2 proxy which does the autentication, and then passes the request to "/realvts" on the same nginx (it has access rules to allow only the internal access on the same machine). "/realvts" is configured with the vhost_traffic_status_display.

If I access with a browser /vts, the oauth2 proxy is doing its job, gives me the content of /realvts, but then the browser tries to access /realvts/format/json instead of /vts/format/json. This is because the html source has var vtsStatusURI = "/realvts//format/json", instead of using a relative url (untested: var vtsStatusURI = "./format/json", or maybe var vtsStatusURI = "format/json",).

So:

  • initial access: browser /vts -> nginx /vts -> oauth2 proxy /realvts -> same nginx /realvts -> vts module
  • wrong subsequent request from the status page: browser /realvts -> nginx /realvts -> 403
  • intended subsequent request from the status page: browser /vts -> nginx /vts -> oauth2 proxy /realvts -> same nginx /realvts -> vts module

This doesn't work, as the browser on a remote system is not allowed to access /realvts.

Bye,
Alexander.

@netchild Hi, sorry for late.

/realvts/format/json instead of /vts/format/json. This is because the html source has var vtsStatusURI = "/realvts//format/json", instead of using a relative url

The below doc says that
vtsStatusURI can be overwritten after the module installed. Thus we can exchange the value of vtsStatusURL to your accessible one, e.g. `vtsStatusURI = “/vts/format/json”

https://github.com/vozlt/nginx-module-vts?tab=readme-ov-file#to-customize-after-the-module-installed

The FreeBSD port doesn't install the template. I have to dig into the sources and give it a try.

Nevertheless, having a relative path instead of an absolute path would reduce the need to modify it and have it simply work out of the box in more cases.

@netchild

Nevertheless, having a relative path instead of an absolute path would reduce the need to modify it and have it simply work out of the box in more cases.

Indeed, we tested to exchange vtsStatusURI to the relative path, it seems work fine below.

WebUI:
スクリーンショット 2024-05-25 10 34 15
html source:
スクリーンショット 2024-05-25 10 37 23

nginx.conf

http {
    include mime.types;
    default_type application/octet-stream;

    vhost_traffic_status_zone shared:vhost_traffic_status:32m;
    vhost_traffic_status_zone;
    #vhost_traffic_status_bypass_upstream_stats off;
    proxy_cache_path /var/cache/nginx keys_zone=zone1:1m max_size=1g inactive=24h;

    upstream backend {
        zone backend 64k;
        server localhost:8080;
    }
    server {
        listen 8081;
        location / {
            proxy_cache zone1;
            proxy_pass http://backend;
            access_log /usr/local/nginx/logs/access.log;
        }
    }
    server {
        listen 8082;
        location /status {
            vhost_traffic_status_display;
            vhost_traffic_status_display_format html;
            vhost_traffic_status_bypass_stats on;
        }
        location /format/json {
            vhost_traffic_status_display;
            vhost_traffic_status_display_format json;
            vhost_traffic_status_bypass_stats on;
        }
    }
    server {
        root html;
        listen 8080;
        location / {
            index  index.html index.htm;
        }
    }
}

if it is applicable for your usecase with above configuration is used, it probably should protect the endpoint /format/json and /status or listening port 8082.

But, it is unnecessary /format/json because the conventional config is satisfy without this. It will have been breaking change.

I don't understand why you have /format/json in the nginx config. The relative path should resolve to /status/./format/json which should resolve to /status/format/json, so this location part should not be needed in the nginx config.

Real world example of what I try:
nginx.conf:

upstream vstatus {
        server localhost:4182;
        keepalive 2;
}

server {
        listen          443 ssl http2 reuseport accept_filter=tlsready;
...
# needed by oauth2 proxy to authenticate
location ~ /voauth2(?<vpath>.*) {
       proxy_redirect off;

       include common.proxy.conf;

       proxy_pass_request_headers on;
       proxy_set_header Connection "keep-alive";
       proxy_store off;
       # internal oauth2 proxy access without TLS for now (testing/debugging)
       proxy_pass http://vstatus/voauth2$vpath$is_args$args;

       gzip on;
       gzip_proxied any;
       gzip_types *;
}

# the path to protect with oauth2 authentication, the real data is at https://server.tld/vstatus which the oauth2 proxy accesses behind the scenes
location ~ /status(?<statpath>.*) {
       proxy_redirect off;

       include common.proxy.conf;

       proxy_pass_request_headers on;
       proxy_set_header Connection "keep-alive";
       proxy_store off;
       # internal oauth2 proxy access without TLS for now (testing/debugging)
       proxy_pass http://vstatus/vstatus$statpath$is_args$args;

       gzip on;
       gzip_proxied any;
       gzip_types *;
}

location /vstatus {
        # https://github.com/vozlt/nginx-module-vts
       allow   127.0.0.1;
       allow   ::1;
       allow <local IPs>;
       deny    all;


        # don't count the status traffic
        vhost_traffic_status_bypass_limit on;
        vhost_traffic_status_bypass_stats on;
        # enable stats display
        vhost_traffic_status_display;
        vhost_traffic_status_display_format html;
}

So the /status goes to the oauth2 proxy, which accesses https://localhost/vstatus after successful auth. The browser only has access to /status, as such it shall retrieve /status/xxx. What the oauth2 proxy fetches is /vstatus/xxx. Any reference inside xxx should ideally be relative, so that the browser generates the URL /status/xyz when he sees "./xyz" or "xyz" instead of "/vstatus/xyz".

While your module only knows about /vstatus, the browser can only access /status. The oauth2 proxy accesses /vstatus, but doesn't rewrite URLs in java script. I can not instruct nginx to rewrite URLs from /vstatus to /status, as the oauth proxy makes the internal request to this URL (which is allowed from localhost). As such the most practical solution is to let the browser construct the real path to access on it's own. A relative path solves this.