Multiple named matchers for basicauth?

1. Caddy version (caddy version):

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

2. How I run Caddy:

using command prompt and Caddyfile

a. System environment:

Ubuntu 20.04 installed with apt install method

b. Command:

caddy run

c. My complete Caddyfile or JSON config:

caddy.local {
        reverse_proxy /jellyfin/* http://192.168.1.7:8096
        @notJellyfinApp {
                not {
                        header X-Requested-With org.jellyfin.mobile
                }
        }
        @notChromecast {
                not {
                        header X-Referer https://apps.jellyfin.org/chromecast/
                }
        }
        basicauth @notJellyfinApp @notChromecast {
                user1 reallylongstringforuser1
                user2 reallylongstringforuser2
        }
}

3. The problem I’m having:

I’m trying to add an extra layer of security on top of the security handled by the app that is being reverse proxied. I want to make it so that requests get a basic auth challenge unless they are
A) a Chromecast which has no way to basic auth through, or
B) the Android app which also has no way to basic auth through.

So in order to do that I’ve created the two named matches
@notJellyfinApp and @notChromecast

I can get both of them to work just like I want, but only one at at time

basicauth @notJellyfinApp {
works just fine by itself, and so does the @notChromecast by itself.

I can’t combine both of my requirements into a single named match because those are treated as AND and in my case I want to be an OR condition. From what I’ve read OR can be done by putting two matches into the statement, but that doesn’t seem to work on the basicauth { option as far as I can tell.

4. Error messages and/or full log output:

caddy validate 2020/05/08 15:52:16.766 INFO using adjacent Caddyfile validate: adapting config using caddyfile: parsing caddyfile tokens for 'basicauth': Caddyfile:17 - Error during parsing: unrecognized hash algorithm: @notChromecast

5. What I already tried:

I’ve tried a few different ways to combine them like
basicauth [@notJellyfinApp @notChromecast] {
basicauth @notJellyfinApp, @notChromecast {
So far I can’t find any other examples of Caddy2 taking two matchers for basicauth, so I’m not sure if it’s just not supported or if I’m missing something obvious. I can’t find anything mentioning multiples for this use case where it’s an OR and not an AND. Request matchers (Caddyfile) — Caddy Documentation
Thanks in advance for your assistance! :slight_smile:

You can’t have multiple matchers on one directive. Instead, merge the rules into one named matcher:

@excluded {
    not {
        header X-Requested-With org.jellyfin.mobile
        header X-Referer https://apps.jellyfin.org/chromecast/
    }
}
1 Like

I tried your fix, but that doesn’t work unfortunately. Now neither situation A) Chromecast or B) Android app work. That exclude is saying “Don’t do basicauth if it’s an Android app AND and Chromecast” which is impossible, the two can’t be happening at the same time, its an OR situation but this fix would work if it was an AND situation.

Any other ideas?

Ah, yeah you’re right, sorry.

I think the simplest approach would be to use a CEL matcher in this situation since you need OR logic.

@excluded {
    expression \
        {http.request.header.x-requested-with} == 'org.jellyfin.mobile' \
        || {http.request.header.x-referer} == 'https://apps.jellyfin.org/chromecast/'
}

I used \ to escape the newlines, I think it’s a bit more readable that way than it being one long line

FYI, in Caddy v2.1, it will be possible to shorten it to this:

@excluded expression \
    {header.x-requested-with} == 'org.jellyfin.mobile' \
    || {header.x-referer} == 'https://apps.jellyfin.org/chromecast/'

Can you have multiple not matchers in the same matcher set? :thinking:

Won’t that just merge them with AND the same as above?

Thank you @francislavoie! That is working now. :tada:

@excluded {
	not {
		expression \
			{http.request.header.x-requested-with} == 'org.jellyfin.mobile' \
			|| {http.request.header.x-referer} == 'https://apps.jellyfin.org/chromecast/'
	}
}

Doing testing in curl with different headers I get the result I was looking for.

curl -I https://caddy.local/jellyfin/ -H "X-Requested-With: org.jellyfin.mobile"
curl -I https://caddy.local/jellyfin/ -H "X-Referer: https://apps.jellyfin.org/chromecast/

Gives an expected 302 to the main URL, and a 200 to exact URLs :tada:

curl -I https://caddy.local/jellyfin/

Anything other than either of those two headers, forces basicauth to happen, which gives a 401 and prompts for auth in browsers that support that. curl just dies unless you feed it with curl -I https://caddy.local/jellyfin/ --user username:password and in that case I get a 302 to the main and 200 to exact URLs which is perfect.

2 Likes

Great!

Turns out that the following doesn’t adapt to the correct JSON:

I’m working on a bug fix. Essentially only the last header matcher is kept within the not block. It’s a Caddyfile parsing bug.

If you have a sec, could you try this and see if it behaves correctly as well (just for my sanity)? I think it should be equivalent to the expression matcher I gave you, based on my conversation with @matt.

@excluded {
    not {
        header X-Requested-With org.jellyfin.mobile
    }
    not {
        header X-Referer https://apps.jellyfin.org/chromecast/
    }
}

Edit: Made a PR to fix the bug I described above

2 Likes

That second config with two not statements also appears to be working correctly on my side. Is there a benefit one way or the other like one being more efficient processing the rules? This system is in a homelab, so load shouldn’t be a concern, it’s more of a curiosity.

Hmm, that’s a good question, regarding performance. CEL matchers are still experimental, we haven’t done a whole lot of performance testing. I would expect that it would be slightly slower than standard matchers though, because there’s more steps involved in passing the data through to the CEL environment.

Since the CEL expression matcher is still considered experimental, I’d probably have to suggest using the regular matchers instead for now, because there’s a chance we change the API.

I also realized you could reduce the syntax a bit further to this:

@excluded {
    not header X-Requested-With org.jellyfin.mobile
    not header X-Referer https://apps.jellyfin.org/chromecast/
}

The not matcher is special in that if you only need to negate one other matcher, it can be done inline. In Caddy v2.1, you’ll also be able to have single-line named matchers as well (like @x not header a b)

1 Like

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