How to OR a matcher set?

1. The problem I’m having:

The documentation for matchers says

A named matcher definition constitutes a matcher set . Matchers in a set are AND’ed together; i.e. all must match. For example, if you have both a header and path matcher in the set, both must match.

Is there a way to use an OR?

An example could be the Authelia’s forward_auth which in my case is

(authenticate-with-authelia) {
	@forceAuth {
		not remote_ip private_ranges
	}
	forward_auth @forceAuth authelia:9091 {
		uri /api/verify?rd=https://authelia.swtk.eu/
		copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
		trusted_proxies private_ranges
	}
}

I would like, in @forceAuth, to say “when remote_ip is not in my private range (that one is there) OR when there is no match on header X-Authelia-Token having the value of a_big_secret (in other words: allow an authenticated call from within my LAN or if there is a specific header).

Is this possible?

I thought that a boolean NOT (a AND b) === (NOT a) OR (NOT b) would make it, but apparently not (I still get authenticated when passing the header, interestingly I am also authenticated from outside the LAN):

	@forceAuth {
		not {
			remote_ip private_ranges
			header x-authelia-token a_big_secret
		}
	}

Caddy version

v2.7.4 h1:J8nisjdOxnYHXlorUKXY75Gr6iBfudfoGhrJ8t7/flI=

Matchers in the Caddyfile are a bit more limited/handicapped from what can be expressed in JSON config.

Unless Francis or Matthew have clever ideas, my suggestion would be to either use the CEL matcher (which lets you combine matchers with advanced boolean logic) or switch to JSON config (which lets you define more matcher sets per handler which do get OR’ed).

I had a look at the JSON that is built from that attempt:

"match": [
  {
    "not": [
      {
        "header": {
          "X-Authelia-Token": [
            "E...w"
          ]
        },
        "remote_ip": {
          "ranges": [
            "192.168.0.0/16",
            "172.16.0.0/12",
            "10.0.0.0/8",
            "127.0.0.1/8",
            "fd00::/8",
            "::1"
          ]
        }
      }
    ]
  }
]

It looks all right to me: ¬(header ∧ remote_ip) ⇒ ¬header ∨ ¬remote_ip, which means “force authentication when no header or not LAN”. Which is what I am looking for.

Not sure why it does not work, I will continue to investigate. I will also look at CEL, I do not know this language (yet :))

1 Like

Close, but you have only one matcher set there: header AND remote_ip (inside not) i.e.: “NOT (header AND remote_ip)”. Different matchers IN a set get “ANDed” together, that’s what lets you combine multiple different parts of a request.

Matcher sets themselves get OR’ed together, though:

"match": [
  {
    "not": [
      {
        "header": {
          "X-Authelia-Token": [
            "E...w"
          ]
        }
      }
    ]
  },
  {
    "not": [
      {
        "remote_ip": {
          "ranges": [
            "192.168.0.0/16",
            "172.16.0.0/12",
            "10.0.0.0/8",
            "127.0.0.1/8",
            "fd00::/8",
            "::1"
          ]
        }
      }
    ]
  }
]

Try that (I just whipped this up here, not sure if my indentation / nesting is right).

This is essentially “(NOT header) OR (not remote_ip)” which I think is what you want?

1 Like

Yes indeed.

OK, got it, thanks.

The solution below now works as expected:

(authenticate-with-authelia) {
	@forceAuth {
		not remote_ip private_ranges
		not header x-authelia-token E...w
		}
	forward_auth @forceAuth authelia:9091 {
		uri /api/verify?rd=https://authelia.swtk.eu/
		copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
		trusted_proxies private_ranges
	}
}
  • Calls from the LAN bypass Authelia (including when they have the header, which is useless but I tried that just as a test case)
  • Calls from the Internet go to Authelia, except if they have the right header and token.
2 Likes

Oh that’s beautiful :slight_smile:

I gave a talk last year which explained how this works:

2 Likes

Oh that’s cool.
And the event was in Lille, 90 minutes from Paris. Hope you enjoyed your first trip to Europe (even though Lille is just one facet of France, one of many)

1 Like

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