Opened 5 weeks ago

Closed 5 weeks ago

Last modified 4 weeks ago

#2013 closed defect (invalid)

auth_basic cannot be overwritten from other context

Reported by: royduineveld@… Owned by:
Priority: minor Milestone:
Component: nginx-core Version: 1.19.x
Keywords: Cc:
uname -a: Linux servername 4.4.0-119-generic #143-Ubuntu SMP Mon Apr 2 16:08:24 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
nginx -V: nginx version: nginx/1.15.0
built with OpenSSL 1.0.2g 1 Mar 2016
TLS SNI support enabled
configure arguments: --with-cc-opt='-g -O2 -fPIE -fstack-protector-strong -Wformat -Werror=format-security -fPIC -Wdate-time -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-Bsymbolic-functions -fPIE -pie -Wl,-z,relro -Wl,-z,now -fPIC' --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --modules-path=/usr/lib/nginx/modules --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-debug --with-pcre-jit --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_v2_module --with-http_dav_module --with-http_slice_module --with-threads --with-http_addition_module --with-http_geoip_module=dynamic --with-http_gunzip_module --with-http_gzip_static_module --with-http_image_filter_module=dynamic --with-http_sub_module --with-http_xslt_module=dynamic --with-stream=dynamic --with-stream_ssl_module --with-stream_ssl_preread_module --with-mail=dynamic --with-mail_ssl_module --add-dynamic-module=/build/nginx-4QY95m/nginx-1.15.0/debian/modules/http-auth-pam --add-dynamic-module=/build/nginx-4QY95m/nginx-1.15.0/debian/modules/http-dav-ext --add-dynamic-module=/build/nginx-4QY95m/nginx-1.15.0/debian/modules/http-echo --add-dynamic-module=/build/nginx-4QY95m/nginx-1.15.0/debian/modules/http-upstream-fair --add-dynamic-module=/build/nginx-4QY95m/nginx-1.15.0/debian/modules/http-subs-filter

Description (last modified by royduineveld@…)

The documentation says: "The special value off allows cancelling the effect of the auth_basic directive inherited from the previous configuration level." Link: http://nginx.org/en/docs/http/ngx_http_auth_basic_module.html#auth_basic

So this should work right?

http {
    auth_basic            "Restricted Area";
    auth_basic_user_file  /htpasswd;

    server {
        location / {
            
        }
        location /api {
            auth_basic off;
        }
    }
}

Or

http {
    server {
        auth_basic            "Restricted Area";
        auth_basic_user_file  /htpasswd;
        
        location / {

        }
        location /api {
            auth_basic off;
        }
    }
}

But at /api I'm still getting the basic authentication. It only works like this:

http {
    server {
        location / {
            auth_basic            "Restricted Area";
            auth_basic_user_file  /htpasswd;
        }
        location /api {
            auth_basic off;
        }
    }
}

So auth_basic can not be overwritten from another context. This also happens with allow/deny. Having this for example at http/server level:

allow 127.0.0.1;
deny all;

Can not be overwritten with allow all; for a specific location.

Change History (10)

comment:1 by royduineveld@…, 5 weeks ago

Description: modified (diff)

comment:2 by Maxim Dounin, 5 weeks ago

Works fine here:

$ curl -I http://127.0.0.1:8080/
HTTP/1.1 401 Unauthorized
Server: nginx/1.19.1
Date: Tue, 07 Jul 2020 12:43:08 GMT
Content-Type: text/html
Content-Length: 179
Connection: keep-alive
WWW-Authenticate: Basic realm="Restricted Area"

$ curl -I http://127.0.0.1:8080/api
HTTP/1.1 404 Not Found
Server: nginx/1.19.1
Date: Tue, 07 Jul 2020 12:43:11 GMT
Content-Type: text/html
Content-Length: 153
Connection: keep-alive

While the version you are using is old and no longer supported, it is expected to work fine as well. Please clarify how do you test things.

comment:3 by royduineveld@…, 5 weeks ago

Just updated to 1.18.0:

nginx version: nginx/1.18.0
built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.12)
built with OpenSSL 1.0.2g  1 Mar 2016
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 --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='-g -O2 -fPIE -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC' --with-ld-opt='-Wl,-Bsymbolic-functions -fPIE -pie -Wl,-z,relro -Wl,-z,now -Wl,--as-needed -pie'

But I'm still having the same problem. All configuration (just for sure):

/etc/nginx/nginx.conf:

user forge;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
        worker_connections 768;
        multi_accept on;
}

http {

        ##
        # Basic Settings
        ##

        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 65;
        types_hash_max_size 2048;
        # server_tokens off;

        server_names_hash_bucket_size 128;
        # server_name_in_redirect off;

        include /etc/nginx/mime.types;
        default_type application/octet-stream;

        ##
        # SSL Settings
        ##

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
        ssl_prefer_server_ciphers on;

        ##
        # Logging Settings
        ##

        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;
        
        ##
        # Gzip Settings
        ##

        gzip on;
        gzip_disable "msie6";

        # gzip_vary on;
        # gzip_proxied any;
        # gzip_comp_level 6;
        # gzip_buffers 16 8k;
        # gzip_http_version 1.1;
        # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

        ##
        # Virtual Host Configs
        ##

        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;
}

/etc/nginx/conf.d/password.conf:

satisfy any;
allow 127.0.0.1;
allow 123.123.123.123;
deny all;

auth_basic            "Restricted Area";
auth_basic_user_file  /home/forge/global.htpasswd;

/etc/nginx/sites-enabled/website.com:

# FORGE CONFIG (DO NOT REMOVE!)
include forge-conf/website.com/before/*;

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name website.com;
    root /home/forge/website.com/public;

    # FORGE SSL (DO NOT REMOVE!)
    ssl_certificate /etc/nginx/ssl/website.com/1/server.crt;
    ssl_certificate_key /etc/nginx/ssl/website.com/1/server.key;

    ssl_protocols TLSv1.2;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384;
    ssl_prefer_server_ciphers on;
    ssl_dhparam /etc/nginx/dhparams.pem;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";

    index index.html index.htm index.php;

    charset utf-8;

    # FORGE CONFIG (DO NOT REMOVE!)
    include forge-conf/website.com/server/*;

    location / {
            try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    access_log off;
    error_log  /var/log/nginx/website.com-error.log error;

    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

# FORGE CONFIG (DO NOT REMOVE!)
include forge-conf/website.com/after/*;

/etc/nginx/forge-conf/website.com/before/ssl_redirect.conf:

# Redirect every request to HTTPS...
server {
    listen 80;
    listen [::]:80;

    server_name .website.com;
    return 301 https://$host$request_uri;
}


# Redirect SSL to primary domain SSL...
server {
        listen 443 ssl http2;
    listen [::]:443 ssl http2;

    # FORGE SSL (DO NOT REMOVE!)
    ssl_certificate /etc/nginx/ssl/website.com/1/server.crt;
    ssl_certificate_key /etc/nginx/ssl/website.com/1/server.key;

    ssl_protocols TLSv1.2;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384;
    ssl_prefer_server_ciphers on;
    ssl_dhparam /etc/nginx/dhparams.pem;

    server_name www.website.com;
    return 301 https://website.com$request_uri;
}

/etc/nginx/forge-conf/website.com/server/letsencrypt_challenge.conf:

location /.well-known/acme-challenge {
auth_basic off;
allow all;
alias /home/forge/.letsencrypt;
}

/etc/nginx/forge-conf/website.com/server/api.conf:

location /api {
    auth_basic off;
    allow all;
}

That last file isn't working. I've removed that file and I debugged it within /etc/nginx/modules-enabled/website.com I'm having the behavior described in the ticket description.

Last edited 5 weeks ago by royduineveld@… (previous) (diff)

in reply to:  3 comment:4 by Maxim Dounin, 5 weeks ago

Replying to royduineveld@…:

But I'm still having the same problem. All configuration (just for sure):

To be sure, please try the exact configuration you've written in the ticket description.

That last file isn't working.

Define "isn't working". How do you test?

Just in case, looking at your configuration suggests that the most likely reason is that the request you do for tests ends up being handled in location ~ \.php$ instead of location /api, and this explains things since the former does require authentication. Consider using location ^~ /api instead (note though that this will prevent *.php processing).

comment:5 by royduineveld@…, 5 weeks ago

I'm testing the same way as you did:

curl -IL https://website.com
HTTP/1.1 401 Unauthorized
Server: nginx/1.18.0
Date: Tue, 07 Jul 2020 14:37:27 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 179
Connection: keep-alive
WWW-Authenticate: Basic realm="Restricted Area"
curl -IL https://website.com/api
HTTP/1.1 401 Unauthorized
Server: nginx/1.18.0
Date: Tue, 07 Jul 2020 14:38:20 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 179
Connection: keep-alive
WWW-Authenticate: Basic realm="Restricted Area"

It doesn't even get to the location ~ \.php$, I've removed that part to test. Even with all "locations" removed I'm still getting the basic auth prompt. But when I do this it's working (200 response instead of a 401):

location /api {
    auth_basic off;
    allow all;
    return 200 "test";
}

But of course I do not just want to return something.

comment:6 by Maxim Dounin, 5 weeks ago

Ok, so please test the configuration you've provided in the ticket description.

comment:7 by royduineveld@…, 5 weeks ago

Found it! I think it's not a bug but maybe this can be explained better in the docs. By specifying anything in de http/server context will be applied to every location. Let's have a look at these:

location / {
        try_files $uri $uri/ /index.php?$query_string;
}


location ~ \.php$ {
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
    fastcgi_index index.php;
    include fastcgi_params;
}

The first one accepts everything without .php and rewrites it to the index.php. So when I do this:

location /api {
    auth_basic off;
    allow all;
}

The auth is removed but then it's rewriting it so it goes to the location ~ \.php$ and the authentication is also applied there so I'm still getting the basic auth prompt. The options:
1) Disable it twice in both the locations
2) Include the fastcgi_params in in the location /api.

Last edited 5 weeks ago by royduineveld@… (previous) (diff)

comment:8 by Maxim Dounin, 5 weeks ago

Resolution: invalid
Status: newclosed

You've claimed you've tested the curl -IL https://website.com/api, and the /api request is only expected to be handled in the location /api, not in location /. And there is no rewrites and/or try_files in the location /api. So your explanation does seem to explain anything.

On the other hand, omitted parts of the configuration might actually explain what happens in your configuration. For example, error_page 404 /something might be the reason if the /api resource does not exist.

Either way, as initially suggested, this looks like a configuration bug, not an nginx issue. Closing this.

comment:9 by royduineveld@…, 5 weeks ago

That makes sense, ended up with:

location /api {
    auth_basic off;
    allow all;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root/index.php;
    fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
}

What I wanted and meant was this:

location /api {
    auth_basic off;
    allow all;
    try_files $uri $uri/ /index.php?$query_string;
}

But then location ~ \.php$ is processing it and disabling the auth doesn't have effect anymore. So it would be nice if try_files passes through the auth_basic off; and allow all; to the next location. Can this ticket be re-opened for that or should I create a new ticket?

comment:10 by Maxim Dounin, 4 weeks ago

So it would be nice if try_files passes through the auth_basic off; and allow all; to the next location. Can this ticket be re-opened for that or should I create a new ticket?

No, it wouldn't be nice, it would be a nightmare to configure things when some parts of the configuration are "passed" to other parts of the configuration. Such approaches are known to be very bad from maintenance point of view, requiring a lot of effort to understand how a particular configuration works and how to modify it correctly. Consider using explicit configurations when you need different processing for requests.

You may also want to watch this talk by Igor Sysoev to better understand how nginx configuration is expected to be used:

https://youtu.be/YWRYbLKsS0I

Hope this helps.

Note: See TracTickets for help on using tickets.