Using a Caddy placeholder as a CEL map

1. The problem I’m having:

I would like to have a variable that contains a map, and I would like to be able to use it from a CEL expression.

Here’s a toy example:

:8080 {
	vars {
		test `{'blue': 0x000080, 'red': 0xFF0000}`

	@foo expression `{'blue': 0x000080, 'red': 0xFF0000}['red'] == 0xFF0000`
	@bar expression `{vars.test}['red'] == 0xFF0000`

	handle @foo {
		respond "MATCH" 200

The @foo matcher works perfectly (it’s one of the examples in the CEL docs). But my attempt in the @bar matcher does not – caddy outputs an error that says “no such key: red”.

Ultimately I would like to use the in CEL operator on the map, but this example will suffice for now.

I guess either I’m missing some kind of escape syntax, or this isn’t possible…

2. Error messages and/or full log output:

2023/03/15 17:14:22.475	INFO	using adjacent Caddyfile
2023/03/15 17:14:22.475	WARN	Caddyfile input is not formatted; run the 'caddy fmt' command to fix inconsistencies	{"adapter": "caddyfile", "file": "Caddyfile", "line": 14}
2023/03/15 17:14:22.475	INFO	admin	admin endpoint started	{"address": "tcp/localhost:2019", "enforce_origin": false, "origins": ["//localhost:2019", "//[::1]:2019", "//"]}
2023/03/15 17:14:22.476	INFO	tls.cache.maintenance	started background certificate maintenance	{"cache": "0xc0008ffab0"}
2023/03/15 17:14:22.478	INFO	tls	cleaning storage unit	{"description": "FileStorage:/home/rob/.local/share/caddy"}
2023/03/15 17:14:22.478	INFO	tls	finished cleaning storage units
2023/03/15 17:14:22.479	INFO	autosaved config (load with --resume flag)	{"file": "/home/rob/.config/caddy/autosave.json"}
2023/03/15 17:14:22.479	INFO	serving initial configuration
2023/03/15 17:14:31.587	ERROR	http.matchers.expression	evaluating expression	{"error": "no such key: red"}
2023/03/15 17:14:31.587	ERROR	http.log.error	no such key: red	{"request": {"remote_ip": "", "remote_port": "34362", "proto": "HTTP/1.1", "method": "GET", "host": "localhost:8080", "uri": "/", "headers": {"User-Agent": ["curl/7.85.0"], "Accept": ["*/*"]}}, "duration": 0.000132772}

3. Caddy version:

% rpm -q caddy

4. How I installed and ran Caddy:

a. System environment:

Fedora 37, installed via dnf.

b. Command:

caddy run

The vars directive isn’t JSON or struct-aware. That’s just encoding that as a string.

Caddy placeholders don’t output the syntactical elements as-is in CEL. CEL expressions are compiled ahead of time (when the config is loaded).

Placeholders in CEL expressions get transformed into a function call to retrieve the value of the placeholder. This happens before the CEL expression is compiled. In this case, the placeholder function call returns a string with the value of the var you set.

The regexp basically turns {vars.test} into caddyPlaceholder(request, "vars.test").

All that said, I’m not sure what you’re trying to achieve. This is a bit of an XY problem - Wikipedia. If you explain what you’re trying to do, I might be able to suggest an alternate solution.

1 Like

Ah ok, that makes sense. I hadn’t realised that the CEL expression was being compiled at the time of loading the config.

I was hoping to use a map in a CEL expression so that I could avoid having to write out lots of matchers for different API keys required to access various endpoints. e.g. /foo requires one key, /bar requires another … etc.

Did you consider using the map directive instead?

I’m not clear on what kind of pattern you’re trying to write. If you can give an example then I could suggest something specific.