Client Certificate or IP Whitelist

1. The problem I’m having:

I’m trying to set up a site where users are only allowed if they come from a whitelisted IP or they have a certificate installed that I have created.
It has proven to a bit of a mountain I’m trying to climb and I am not entirely sure this is even possible.

I can’t seem to find any OR matcher definition in the documentation.

Is it not possible to check for either valid IP or valid self signed certificate?

2. Error messages and/or full log output:

No errors, yet, since I can't find any OR matcher.

3. Caddy version:

v2.6.4

4. How I installed and ran Caddy:

Installed Caddy with the caddy_windows_amd64.exe file downloaded from the main site with 0 extra features selected. Installed as a service with WinSW (Keep Caddy Running — Caddy Documentation).

a. System environment:

Windows 10 Pro 22H2 build 19045.2486

b. Command:

Caddy Service is set up from GUIDE to run the exe file

caddy.exe

I reload the caddyfile configuration with

caddy reload

c. Service/unit/compose file:

Not relevant

d. My complete Caddy config:

(allowed) {
	@blocked not {
		remote_ip 10.0.0.0/24
		remote_ip 195.249.218.110
		remote_ip 195.249.218.111
	}
}

(blocked) {
	respond @blocked "I have gone and will not return" 410
}

hass.sewers.dk {
	encode gzip
	import allowed
	import blocked
	reverse_proxy http://10.0.0.154:8123
}

You can shorten it to this:

@blocked not remote_ip 10.0.0.0/24 195.249.218.110 195.249.218.111

For client certificates, you’ll need to configure tls > client_auth tls (Caddyfile directive) — Caddy Documentation, and since you want to allow connections without a client cert (it’s checked during the TLS handshake, before any request handling), you should use verify_if_given.

Then, you can use client cert placeholders (see Caddyfile Concepts — Caddy Documentation) to get properties from the cert, or if you don’t care about a specific value, you can just check if they’re not empty.

You can use the vars matcher to match on a value from a placeholder, using "" as the value to test for it being empty (I think, haven’t tried this in a while).

The tricky part is getting an OR in the condition. I gave a talk on this topic last year https://youtu.be/QSerOHpMjgY?t=10m41s, you can see that using the not matcher, you can create an OR using De Morgan’s laws.

I think this might work:

@blocked not {
	not not remote_ip 10.0.0.0/24 195.249.218.110 195.249.218.111
	not vars {tls_client_subject} ""
}

In pseudocode, it would read like this:

not ( (not not remote_ip) AND (not vars) )

And this can be transformed into this:

not (not not remote_ip) OR not (not vars)

Then simplified to this by removing the double negatives which cancel out:

not remote_ip OR vars

You could also use an expression matcher though is much simpler to read (although you can’t use vars which doesn’t exist in expressions currently, but you can use placeholders directly so you get the same effect):

@blocked `!remote_ip('10.0.0.0/24', '195.249.218.110', '195.249.218.111') || {tls_client_subject} == ''`

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