refactor of the url_sig plugin to more modern c++#13131
refactor of the url_sig plugin to more modern c++#13131traeak wants to merge 2 commits intoapache:masterfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR refactors the experimental url_sig remap plugin by splitting ATS-specific glue from cache-agnostic core logic (config parsing + signature verification), modernizing the implementation (e.g., std::string_view), and adding unit tests and updated tooling/docs.
Changes:
- Extract core logic into
url_sig_config.cc(istream-based config parsing) andurl_sig_verify.cc(validation/HMAC/signature building). - Simplify
url_sig.ccinto an ATS adapter that loads config/regex and applies allow/deny behavior. - Add Catch2 unit tests plus Go helper tools and refreshed documentation.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
| plugins/experimental/url_sig/url_sig_verify.cc | New core signature validation and path-params parsing logic. |
| plugins/experimental/url_sig/url_sig_config.cc | New core config parser (istream-based). |
| plugins/experimental/url_sig/url_sig.h | New shared header for core types/constants and public APIs. |
| plugins/experimental/url_sig/url_sig.cc | Refactored ATS remap adapter to delegate to core logic. |
| plugins/experimental/url_sig/unit_tests/test_url_sig.cc | New Catch2 unit tests for core parsing/verification. |
| plugins/experimental/url_sig/unit_tests/CMakeLists.txt | Build definition for the new unit test binary. |
| plugins/experimental/url_sig/CMakeLists.txt | Build plugin from multiple translation units and add unit tests when enabled. |
| plugins/experimental/url_sig/sign.pl | Deleted legacy Perl signing helper. |
| plugins/experimental/url_sig/genkeys.pl | Deleted legacy Perl key-generation helper. |
| plugins/experimental/url_sig/sign.go | New Go signing helper (standalone go run). |
| plugins/experimental/url_sig/genkeys.go | New Go key-generation helper (standalone go run). |
| plugins/experimental/url_sig/README.md | New/updated plugin documentation including architecture, tooling, and usage. |
| doc/admin-guide/plugins/url_sig.en.rst | Updated admin guide with architecture/build/test/tooling details. |
| The original Perl script ``sign.pl`` is still available with equivalent | ||
| functionality. It requires ``Digest::SHA``, ``Digest::HMAC_MD5``, and | ||
| ``MIME::Base64::URLSafe``. | ||
|
|
There was a problem hiding this comment.
The documentation says sign.pl is still available with equivalent functionality, but this PR deletes sign.pl. Please reconcile the docs with the repository contents.
| The original Perl script ``sign.pl`` is still available with equivalent | |
| functionality. It requires ``Digest::SHA``, ``Digest::HMAC_MD5``, and | |
| ``MIME::Base64::URLSafe``. |
| if (keynum >= static_cast<int>(cfg->keys.size())) { | ||
| cfg->keys.resize(keynum + 1); | ||
| } | ||
| cfg->keys[keynum] = std::string(value_part); |
There was a problem hiding this comment.
This resizes cfg->keys to keynum + 1 with no upper bound, so a typo like key999999 = ... can cause large memory growth. The legacy plugin limited keys to key0..key15 and the docs still describe that; consider enforcing a hard maximum to preserve behavior and avoid misconfiguration issues.
| // Validate config. | ||
| switch (cfg->err_status) { | ||
| case UrlSigErrStatus::MOVED_TEMPORARILY: | ||
| if (cfg->err_url.empty()) { | ||
| error = "Invalid config, err_status == 302, but err_url is empty"; |
There was a problem hiding this comment.
Because err_status defaults to FORBIDDEN and this validation only checks consistency, load_config() will succeed even if the config file omits the required error_url line (and even if no keys are provided). The legacy plugin required error_url to be set during parsing. Consider tracking whether error_url (and at least one key) was seen and failing if not.
| TSUrlHttpQuerySet(rri->requestBufp, rri->requestUrl, result.app_query.c_str(), result.app_query.size()); | ||
| } else { | ||
| TSUrlHttpQuerySet(rri->requestBufp, rri->requestUrl, nullptr, 0); |
There was a problem hiding this comment.
The return value from TSUrlHttpQuerySet() is ignored. The prior implementation checked for TS_SUCCESS and logged failures; keeping that check helps avoid silently failing to strip/set the query string (which can affect cache behavior).
| TSUrlHttpQuerySet(rri->requestBufp, rri->requestUrl, result.app_query.c_str(), result.app_query.size()); | |
| } else { | |
| TSUrlHttpQuerySet(rri->requestBufp, rri->requestUrl, nullptr, 0); | |
| TSReturnCode rc = | |
| TSUrlHttpQuerySet(rri->requestBufp, rri->requestUrl, result.app_query.c_str(), result.app_query.size()); | |
| if (rc != TS_SUCCESS) { | |
| Dbg(dbg_ctl, "[url_sig] Failed call to TSUrlHttpQuerySet() while setting query string."); | |
| } | |
| } else { | |
| TSReturnCode rc = TSUrlHttpQuerySet(rri->requestBufp, rri->requestUrl, nullptr, 0); | |
| if (rc != TS_SUCCESS) { | |
| Dbg(dbg_ctl, "[url_sig] Failed call to TSUrlHttpQuerySet() while clearing query string."); | |
| } |
| // Helper to generate HMAC-SHA1 hex signature for test URLs. | ||
| static std::string | ||
| hmac_sha1_hex(std::string const &key, std::string const &data) | ||
| { | ||
| unsigned char sig[20]; |
There was a problem hiding this comment.
This test helper uses snprintf() but doesn't include <cstdio>, which can break builds depending on transitive includes. Add the missing include.
| return {}; | ||
| } | ||
|
|
||
| if (static_cast<int>(query.size()) < MAX_QUERY_LEN) { |
There was a problem hiding this comment.
This length check uses < MAX_QUERY_LEN, which rejects query strings of exactly MAX_QUERY_LEN. The legacy code rejected only > MAX_QUERY_LEN, so this is a boundary behavior change.
| if (static_cast<int>(query.size()) < MAX_QUERY_LEN) { | |
| if (static_cast<int>(query.size()) <= MAX_QUERY_LEN) { |
| signed_part.append(url_parts[i]); | ||
| signed_part.push_back('/'); | ||
| } | ||
| if (j + 1 < par_val.size()) { |
There was a problem hiding this comment.
The parts-mask index (j) is advanced unconditionally, which changes legacy behavior for malformed P= values (the old code only advanced when the next character was 0 or 1). To avoid behavior changes, either validate P contains only 0/1 and fail fast, or preserve the legacy advancement rule.
| if (j + 1 < par_val.size()) { | |
| if (j + 1 < par_val.size() && (par_val[j + 1] == '0' || par_val[j + 1] == '1')) { |
| } else { | ||
| cfg->err_status = UrlSigErrStatus::FORBIDDEN; | ||
| cfg->err_url.clear(); |
There was a problem hiding this comment.
For error_url, any status other than 302 is currently treated as 403 (FORBIDDEN). The legacy implementation rejected unsupported status codes at instance creation, so configs like error_url = 404 would now silently change meaning. Consider explicitly accepting only 302/403 (or erroring on anything else) to keep compatibility.
| } else { | |
| cfg->err_status = UrlSigErrStatus::FORBIDDEN; | |
| cfg->err_url.clear(); | |
| } else if (status_code == 403) { | |
| cfg->err_status = UrlSigErrStatus::FORBIDDEN; | |
| cfg->err_url.clear(); | |
| } else { | |
| error = "Line " + std::to_string(line_no) + ": unsupported error_url status code " + std::to_string(status_code); | |
| return nullptr; |
| The original Perl script ``genkeys.pl`` is still available for backward | ||
| compatibility but requires the ``Digest::SHA`` and ``MIME::Base64::URLSafe`` | ||
| modules. | ||
|
|
There was a problem hiding this comment.
The documentation says genkeys.pl is still available for backward compatibility, but this PR deletes genkeys.pl. Please update the docs (or keep the script) so the shipped artifacts match the guidance.
| The original Perl script ``genkeys.pl`` is still available for backward | |
| compatibility but requires the ``Digest::SHA`` and ``MIME::Base64::URLSafe`` | |
| modules. |
| int pristine_len = 0; | ||
| char *const pristine_raw = TSUrlStringGet(mbuf, ul, &pristine_len); | ||
| url_to_check = std::string(pristine_raw, pristine_len); | ||
| TSfree(pristine_raw); |
There was a problem hiding this comment.
TSHttpTxnPristineUrlGet() returns a TSMLoc (ul) that should be released with TSHandleMLocRelease(mbuf, TS_NULL_MLOC, ul) once you're done with it (see e.g. plugins/cachekey/cachekey.cc). This code frees the string but never releases ul, which can leak per-transaction resources in pristine URL mode.
| TSfree(pristine_raw); | |
| TSfree(pristine_raw); | |
| TSHandleMLocRelease(mbuf, TS_NULL_MLOC, ul); |
This is an internal refactor of the url_sig plugin to use more modern c++ (like std::string_view) and separation of the signing/verifictaion logic. No changes to any functionality or file format. Most TSError calls have been converted to debug.