Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 11, 03:30 UTC
caddyhttp: add CEL expression support to the tls matcher (#8088)
* caddyhttp: add CEL expression support to the tls matcher MatchTLS was the only core HTTP matcher without a CELLibrary() implementation, so it could not be used inside an expression matcher. Every sibling matcher (host, path, path_regexp, method, query, header, header_regexp, protocol, remote_ip, client_ip, url_pattern, vars, vars_regexp, and the file-server's file matcher) already has this. Adds CELLibrary() to MatchTLS, exposing tls_handshake_complete('true') / tls_handshake_complete('false') in CEL expressions. Mirrors MatchProtocol's existing string-argument-macro pattern: CELMatcherImpl with cel.StringType, parsing the argument with strconv.ParseBool and constructing a MatchTLS{HandshakeComplete: &b} that reuses the matcher's own MatchWithError. Adds the corresponding CELLibraryProducer interface guard. Adds 6 cases to celmatcher_test.go's existing table-driven TestMatchExpressionMatch/BenchmarkMatchExpressionMatch: match on 'true'/'false' against a complete/incomplete handshake, and three invocation-error cases (no args, wrong arg type, non-boolean string). Required adding a tlsHandshakeComplete *bool field to the shared test table and wiring it into req.TLS.HandshakeComplete in both the test and benchmark request-construction helpers. Fixes #8087 * address review: use a native bool arg, rename to match the matcher's own name francislavoie's review on #8088: - "A boolean as a string argument is very strange." CELMatcherImpl only supported string/list(string)/map argument types, so the original implementation parsed a string literal with strconv.ParseBool to work around that. Instead, extends CELMatcherImpl itself with a cel.BoolType case (celMatcherBoolMacroExpander + isCELBoolExpr, mirroring the existing string equivalents), so a matcher can accept a real CEL bool literal. This is shared infrastructure, so any other matcher needing a bool argument in the future gets it for free. - "I'm not sure why tls_handshake_complete was chosen when the matcher is named tls." Renamed the CEL function to tls(...), matching every other matcher's naming convention (the function name mirrors the matcher's own directive/module name: host(), path(), method(), protocol(), etc.). Usage is now 'expression tls(true)' / 'expression tls(false)' instead of 'expression tls_handshake_complete(\'true\')'. Updated the 6 existing test cases accordingly; still covers match/no-match on both true/false and the two invocation-error cases (no args, wrong arg type -- now a string instead of a bool, confirming the new type check). * address review: cover tls() against a plain non-TLS request steadytao asked for a test proving tls(false) doesn't treat the absence of TLS as an incomplete handshake. MatchWithError already handles this correctly (r.TLS == nil returns false regardless of HandshakeComplete), this just adds the missing coverage for both tls(true) and tls(false) against a plain http:// request. Confirmed these tests catch a real regression: removing the r.TLS == nil guard causes a nil pointer dereference panic, not just a wrong result. * address review: redesign tls() CEL matcher to preserve all three Caddyfile states The Caddyfile tls matcher has three states: bare `tls` (any TLS connection, regardless of handshake completion), `tls early_data` (handshake not yet complete), and the implicit "handshake complete" case. The previous tls(true)/tls(false) design could only express the latter two, with no way to express "any TLS, don't care about handshake state" from CEL. Redesigns the macro to: tls() -- any TLS connection (mirrors bare `tls`) tls('complete') -- handshake complete tls('early_data') -- handshake not yet complete (mirrors `tls early_data`) tls() is implemented as sugar for tls('any'): a zero-arg macro that expands to the same 2-arg call the one-arg string macro produces, just with the literal injected by the macro itself. Both macros share one function registration and one matcher factory, so there's a single source of truth for the string-to-MatchTLS mapping. This replaces the native-bool design from the prior commit, so also removes the generic bool-argument support (celMatcherBoolMacroExpander, isCELBoolExpr, and the CELMatcherImpl case for cel.BoolType) added for it, since nothing else in the codebase used it and it would otherwise ship as unreviewed, unused surface area. Rewrites the existing tls(...) test cases for the new string-based calls, and adds coverage for the new tls() form: matches regardless of handshake state (both complete and incomplete), and still correctly fails to match a plain non-TLS request. Also covers the new invocation-error cases: a boolean argument is no longer valid, and an unrecognized string (anything other than 'complete'/'early_data') is now a Provision-time error instead of silently doing the wrong thing. Verified: full caddyhttp test suite passes, and confirmed each new/changed test fails when its corresponding implementation piece is reverted (the zero-arg macro, and the complete/early_data boolean mapping). * address review: document and test tls('any') as a supported literal form steadytao pointed out that the zero-arg tls() macro expands internally to the same tls_request_string(req, "any") call that the one-arg form uses, which means tls('any') was already silently reachable as a literal string even though it was never documented or tested as part of the public API. Rather than special-casing the macro to bypass the shared string factory (which would need a second function/overload just for the zero-arg case), document and test tls('any') as a real, supported third spelling alongside tls() -- they're already fully equivalent, so there's nothing to hide.