V2 basic auth matcher precedence

1. My Caddy version (caddy version):

v2.0.0 h1:pQSaIJGFluFvu8KDGDODV8u4/QRED/OPyIR+MWYYse8

2. How I run Caddy:

a. System environment:

Ubuntu 16.04.6 LTS, systemd

b. Command:

via systemd

c. Service/unit/compose file:

[Unit]
Description=Caddy
Documentation=https://caddyserver.com/docs/
After=network.target

[Service]
User=caddy
Group=caddy
ExecStart=/usr/bin/caddy run --environ --config /etc/caddy/Caddyfile
ExecReload=/usr/bin/caddy reload --config /etc/caddy/Caddyfile
TimeoutStopSec=5s
LimitNOFILE=1048576
LimitNPROC=512
PrivateTmp=true
ProtectSystem=full
AmbientCapabilities=CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target

d. My complete Caddyfile or JSON config:

{
        email webmaster@***.***
}

(headers) {
        header {
                -Server
                X-Robots-Tag "none"
                Referrer-Policy "same-origin"
                X-Frame-Options "SAMEORIGIN"
                X-Xss-Protection "1; mode=block"
                X-Content-Type-Options "nosniff"
                Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        }
}

https://a***.b***.c** {
        root * /var/www/html
        encode gzip
        import headers
        basicauth {
                 user_1 hashed_pwd_1
        }
        basicauth /projectA/* {
                user_1 hashed_pwd_1
                user_2 hashed_pwd_2
        }
        basicauth /projectB/* {
                user_1 hashed_pwd_1
                user_3 hashed_pwd_3
        }
        respond /conf/* 403 {
                close
        }
        respond /projectA/goodies/* 403 {
                close
        }
        file_server {
                index index.html index.php
        }
        php_fastcgi unix//run/php/php7.0-fpm.sock
}

3. The problem I’m having:

I am having issues with what seems to the the precedence of multiple basicauth directives. I have a file server with a root directory, and two subdirectories - /projectA and /projectB. I would like to have a master user/pwd combination that protects the entire site (root directory down), and then create separate user/pwd combinations which will grant access to each subdirectory. With the current configuration, the user_2 and user_3 credentials don’t work. The user_1 credentials work as they should.

4. Error messages and/or full log output:

The user_2 and user_3 login credentials don’t grant access

5. What I already tried:

Changing the order of the 3 basicauth directives had no effect, nor did wrapping them in a route block to try and force a certain order. When I comment out the first (global) basicauth directive, the user_2 and user_3 credentials work in the subdirectories as they should, but now the root directory is unprotected.

6. Links to relevant resources:

Hi @mpg, welcome to the Caddy community!

I can see how this one is confusing. In Caddy v1, only one basicauth would be executed, based on the longest matching base path.

In v2, things are quite a bit different under the hood.

In your case, multiple basicauth handlers are being evaluated whenever applicable. They are evaluated sequentially, and are not cooperative, so for a path where multiple basicauth handlers are configured, each one will require, in turn, the Authorization header to match and issue a rejection if that requirement is not met.

This means that because you have the entire site protected with user_1's credentials, only those credentials would ever apply. If you didn’t also include user_1's credentials in the /projectA/* configuration, no credentials would ever work, because two modules would have mutually exclusive requirements for this path!

How do we fix that?

Well, you’ll want to ensure that only one basicauth is ever applied for a given path. You could do this with advanced matchers, but you’d forever be coding in exceptions on every other basicauth every time you added one and it’d shortly become a very impressive list of matchers!

Instead, you might have some luck with the handle directive. It groups directives within it as mutually exclusive to other handle groups. The first matching handle group is evaluated, and then no others. That means you can handle the basicauth on your project folders first, then have a web root handle group for the site-wide basicauth to “catch-all” requests that don’t fall under any other handle group.

It’d look like this:

example.com {
  root * /var/www/html
  encode gzip
  import headers

  handle /projectA/* {
    basicauth {
      user_1 hashed_pwd_1
      user_2 hashed_pwd_2
    }
  }

  handle /projectB/* {
    basicauth {
      user_1 hashed_pwd_1
      user_3 hashed_pwd_3
    }
  }

  handle {
    basicauth {
      user_1 hashed_pwd_1
    }
  }

  @close {
    path /conf/*
    path /projectA/goodies/*
  }
  respond @close 403 {
    close
  }

  php_fastcgi unix//run/php/php7.0-fpm.sock
  file server
}

P.S. While I was at it, I removed the index subdirective from your file_server as php_fastcgi acts first and looks for PHP indexes already.

P.S.S. I also demonstrate a slightly different format for your 403 responders, but this ones pretty much purely down to personal preference.

2 Likes

Thanks very much @Whitestrake, worked like a charm.

One question about your P.S.S. - it works perfectly, but my understanding was that multiple matchers within a named set get AND’d together, not OR’d. What am I missing?

You’re correct that multiple different matchers are AND’d. However, because we’re supplying multiple values to the path matcher specifically, they get merged into one and then OR’d, see;

For most matchers that accept multiple values, those values are OR’ed; i.e. one must match in order for the matcher to match.
—Request matchers (Caddyfile) — Caddy Documentation

And in essence,

path /foo
path /bar

Is functionally identical to:

path /foo /bar

Another alternative would be to escape the newline:

path /foo \
     /bar

It’s effectively just a matter of how you like to format the Caddyfile. I think multiple path lines look neatest, but perhaps the most obvious in its intent is the escaped-newline variant.

1 Like

@matt FWIW I think that wording could be clarified to mention that matchers with the same name are OR’ed

1 Like

@francislavoie agreed! Thanks to both of you for the help!

Same type might be better than same name, I’d wager, but the best possible way to communicate this would be with an example and explanation. I’m sure that section would benefit from a practical demonstration of how that scenario is evaluated, for sure.

Uh, where are you using a matcher like this:

path /foo
path /bar

in the above config anywhere? I don’t see it.

Right there

1 Like

Thanks. I didn’t realize the code block scrolled…

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.