Filter access by header value

1. Caddy version (caddy version):

v2.5.1

2. How I run Caddy:

Command line (CLI)

a. System environment:

Windows Server 2016

b. Command:

caddy_windows_amd64.exe run --watch

c. Service/unit/compose file:

N/A

d. My complete Caddyfile or JSON config:

Server2016, localhost, 127.0.0.1, 10.10.7.10 {
	log {
		level INFO
		format console
		output file ./log/caddy.log
	}
	tls ./certs/server.pem ./certs/server.key {
		client_auth {
			mode 					require_and_verify
			trusted_leaf_cert_file	./certs/clients/client01.cer
			trusted_leaf_cert_file	./certs/clients/client02.cer
			trusted_leaf_cert_file	./certs/server.pem
		}
	}

	handle_path /fileserver1/* {
		reverse_proxy * localhost:7777
	}
	
	handle_path /fileserver2/* {
	expression {client_common_name}.startsWith("client01") {
		rewrite * /client01{uri}
	}
	expression {client_common_name}.startsWith("client02") {
		rewrite * /client02{uri}
	}
	reverse_proxy localhost:8888
	}
	route * {
		respond "Main Page!!!"
	}
}

3. The problem I’m having:

We would like to be able to filter a client certificate to a sub directory base upon the client_common_name on the header of client certificate presented.

This would be sub folders access/associated to a unique certificate, like how a home folder per user account would be structured.

Hopefully my explanation is clear enough, I am very new to Caddy so I might be missing something glaringly obvious or it might not even be a option but we’re trying to explore every avenue for help.

4. Error messages and/or full log output:

Log output from caddy.log output file command above.

1.6560244993440115e+09 info http.log.access.log0 handled request {“request”: {“remote_ip”: “fe80::a09c:786e:713:1a3%Ethernet0”, “remote_port”: “49758”, “proto”: “HTTP/2.0”, “method”: “HEAD”, “host”: “nsync”, “uri”: “/fileserver1/xxsgjuv2.4sb”, “headers”: {“User-Agent”: [“rclone/v1.57.0”]}, “tls”: {“resumed”: false, “version”: 772, “cipher_suite”: 4867, “proto”: “h2”, “server_name”: “Server2016”, “client_common_name”: “client01”, “client_serial”: “33652285”}}, “user_id”: “”, “duration”: 0.0010017, “size”: 0, “status”: 200, “resp_headers”: {“Content-Length”: [“4485120”], “Content-Type”: [“application/octet-stream”], “Last-Modified”: [“Sun, 29 May 2022 03:50:09 GMT”], “Date”: [“Thu, 23 Jun 2022 22:48:19 GMT”], “Server”: [“Caddy”, “rclone/v1.57.0”], “Accept-Ranges”: [“bytes”]}}

5. What I already tried:

I have tried using expression {client_common_name}.startsWith(“client01”) and expression {tls_client_subject}.startsWith(“CN=client01”) to validate the client trying to access then use the provided rewrite * /client01{uri}

Using this syntax in the config does not load the config in caddy.

expression {client_common_name}.startsWith(“client01”) {
rewrite * /client01{uri}
}
expression {client_common_name}.startsWith(“client02”) {
rewrite * /client02{uri}
}
reverse_proxy localhost:8888

6. Links to relevant resources:

Request matchers (Caddyfile) — Caddy Documentation (caddyserver.com)

expression is not a directive, it’s a matcher. Please read the docs on request matchers to understand how to use them. You’ll need a named matcher for this.

FYI if you use require_and_verify, all clients will need to present a client certificate, no matter the path they’re requesting. TLS client cert verification happens before the request is handled (because it’s part of the TLS handshake, before the HTTP bytes are decrypted).

1 Like

@francislavoie I have tried to follow the sample using the request matchers.

	@postfoo {
		method GET
        }
	reverse_proxy @postfoo localhost:9999

I get a this line error.

http.stdlib     http: TLS handshake error from 10.10.7.11:49797: EOF

Using Caddyfile:

Server2016, localhost, 127.0.0.1, 10.10.7.10 {
	log {
		level INFO
		format console
		output file ./log/caddy.log
	}
	tls ./certs/server.pem ./certs/server.key {
		client_auth {
			mode 				request
			trusted_leaf_cert_file	./certs/clients/client01.cer
			trusted_leaf_cert_file	./certs/clients/client02.cer
			trusted_leaf_cert_file	./certs/server.pem
		}
	}

	@postfoo {
		method GET
        }

	reverse_proxy @postfoo localhost:9999
}

This is the output from caddy console using run --watch:

2022/06/28 16:08:03.061 [34mINFO[0m   watcher config file changed; reloading  {"config_file": "Caddyfile"}
2022/06/28 16:08:03.064 [34mINFO[0m   using provided configuration    {"config_file": "Caddyfile", "config_adapter": ""}
2022/06/28 16:08:03.066 [33mWARN[0m   Caddyfile input is not formatted; run the 'caddy fmt' command to fix inconsistencies    {"adapter": "caddyfile", "file": "Caddyfile", "line": 14}
2022/06/28 16:08:03.067 [34mINFO[0m   admin   admin endpoint started  {"address": "tcp/localhost:2019", "enforce_origin": false, "origins": ["//localhost:2019", "//[::1]:2019", "//127.0.0.1:2019"]}
2022/06/28 16:08:03.068 [34mINFO[0m   tls.cache.maintenance   started background certificate maintenance      {"cache": "0xc0004b85b0"}
2022/06/28 16:08:03.069 [35mDEBUG[0m  tls.cache       added certificate to cache      {"subjects": ["client01"], "expiration": "2025/04/02 13:34:49.000", "managed": false, "issuer_key": "", "hash": "9109fa33640de2cce2c0f429df016b5a44b1c3c6845803bf44ac2175d90b8f50", "cache_size": 1, "cache_capacity": 10000}
2022/06/28 16:08:03.069 [33mWARN[0m   http    skipping automated certificate management for server because it is disabled     {"server_name": "srv0"}
2022/06/28 16:08:03.069 [34mINFO[0m   http    enabling automatic HTTP->HTTPS redirects        {"server_name": "srv0"}
2022/06/28 16:08:03.070 [33mWARN[0m   http    enabling strict SNI-Host enforcement because TLS client auth is configured      {"server_id": "srv0"}
2022/06/28 16:08:03.071 [34mINFO[0m   admin   stopped previous server {"address": "tcp/localhost:2019"}
2022/06/28 16:08:03.072 [34mINFO[0m   pki.ca.local    root certificate is already trusted by system   {"path": "storage:pki/authorities/local/root.crt"}
2022/06/28 16:08:03.074 [35mDEBUG[0m  http    starting server loop    {"address": "[::]:443", "http3": false, "tls": true}
2022/06/28 16:08:03.075 [35mDEBUG[0m  http    starting server loop    {"address": "[::]:80", "http3": false, "tls": false}
2022/06/28 16:08:03.081 [34mINFO[0m   tls.cache.maintenance   stopped background certificate maintenance      {"cache": "0xc00055f490"}
2022/06/28 16:08:03.085 [34mINFO[0m   autosaved config (load with --resume flag)      {"file": "C:\\Users\\Administrator\\AppData\\Roaming\\Caddy\\autosave.json"}
2022/06/28 16:08:19.246 [35mDEBUG[0m  tls.handshake   choosing certificate    {"identifier": "client01", "num_choices": 1}
2022/06/28 16:08:19.249 [35mDEBUG[0m  tls.handshake   custom certificate selection results    {"identifier": "client01", "subjects": ["client01"], "managed": false, "issuer_key": "", "hash": "9109fa33640de2cce2c0f429df016b5a44b1c3c6845803bf44ac2175d90b8f50"}
2022/06/28 16:08:19.250 [35mDEBUG[0m  tls.handshake   matched certificate in cache    {"subjects": ["client01"], "managed": false, "expiration": "2025/04/02 13:34:49.000", "hash": "9109fa33640de2cce2c0f429df016b5a44b1c3c6845803bf44ac2175d90b8f50"}
2022/06/28 16:08:19.287 [35mDEBUG[0m  http.stdlib     http: TLS handshake error from 10.10.7.11:49797: EOF
2022/06/28 16:08:20.923 [35mDEBUG[0m  tls.handshake   choosing certificate    {"identifier": "client01", "num_choices": 1}
2022/06/28 16:08:20.938 [35mDEBUG[0m  tls.handshake   custom certificate selection results    {"identifier": "client01", "subjects": ["client01"], "managed": false, "issuer_key": "", "hash": "9109fa33640de2cce2c0f429df016b5a44b1c3c6845803bf44ac2175d90b8f50"}
2022/06/28 16:08:20.938 [35mDEBUG[0m  tls.handshake   matched certificate in cache    {"subjects": ["client01"], "managed": false, "expiration": "2025/04/02 13:34:49.000", "hash": "9109fa33640de2cce2c0f429df016b5a44b1c3c6845803bf44ac2175d90b8f50"}

That might be harmless. If some unknown client tried to hit your server without using a hostname, then the TLS handshake would fail. It might be a bot/crawler/whatever.

Are you sure that log comes from a request you made? If so, how did you make the request? Use curl -v to make a request and show what result you get.

1 Like

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