oscerd opened a new pull request, #27811: URL: https://github.com/apache/camel/pull/27811
## Backport of #27719 Cherry-pick of #27719 onto `camel-4.18.x`. **Original PR:** #27719 - CAMEL-25578: camel-lra - verify participant callbacks with an optional shared secret **Original author:** @oscerd **Target branch:** `camel-4.18.x` Not a straight cherry-pick, so it goes through a normal review: - CAMEL-24147 (URL-encoding of the callback parameters, `ec619f2e17df`) is backported first, in its own commit: the signature relies on the encoded parameters, and `camel-4.18.x` did not have it. - `LRASagaRoutes` and `LRASagaService` are ported by hand, as this branch has a single URI allowlist (it predates CAMEL-24192): the participant routes get a compensate and a complete entry point, as the signature is bound to the callback path, and the signed step URIs are checked against that allowlist. Without a secret, the routes behave as before. - `callbackSecret` uses `@Metadata(secret = true)` (this branch has no `security` attribute), `callbacksecret` is added to this branch's own extra sensitive keys, the reserved option names test is in `LRASagaServiceTest`, and the tests use `camel-test-junit5`. - The upgrade-guide notes are left out, as they live on `main` (the 4.18.6 note is already in main's `camel-4x-upgrade-guide-4_18.adoc`). Tested on the branch: the camel-lra unit tests (72), a full reactor build without tests, and the `LRA*IT` tests against the LRA coordinator container: 14 of 15 pass, including `LRASignedCallbacksIT`, so the coordinator calls back the signed URLs. `LRAFailuresIT.testCompletionAfterFailures` fails on `camel-4.18.x` with and without this change, as on #27772: the branch does not have the CAMEL-23905 test fix that triggers the coordinator recovery. ### Original description ## Description [CAMEL-25578](https://issues.apache.org/jira/browse/CAMEL-25578) The LRA participant routes (`PUT <localParticipantContextPath>/compensate` and `/complete`) take the saga step endpoint and the saga options from the callback URL that the participant enlisted with the LRA coordinator. This adds an optional shared secret to the LRA saga service, so the participant can sign those callback URLs and verify them when the coordinator calls back. - New `callbackSecret` option on `LRASagaService`, also available as `camel.lra.callbackSecret` in camel-main (`security = "secret"`). - When it is set, `LRAClient.join()` adds the LRA (`Camel-Saga-Lra`) and a `Camel-Saga-Signature` query parameter to the compensate and complete URLs. The signature is an HMAC-SHA256 over the callback path and every query parameter (step URIs, saga options and the LRA), sorted and encoded one by one, so it does not depend on how the coordinator orders or re-encodes the URL. Each URL is signed for its own path, so a compensate URL is not accepted on `/complete`, and for its own LRA, so it is not accepted for another one. - `LRASagaRoutes` verifies the signature, in constant time, against the raw query (`CamelHttpRawQuery`, else `CamelHttpQuery`, which camel-netty-http decodes) before the existing allowlist check. A missing or invalid signature, or a signed URL without an LRA, gets HTTP 403 and is not routed any further. When it is valid, the step URIs and the saga options are set from the signed parameters, and `Long-Running-Action` from the signed LRA, so neither a request header with the same name as an option nor the LRA named by the request reaches the step endpoint. The signature is removed from both query headers before the step route. In this mode the step URIs come only from the signed parameters: after a valid signature the route goes straight to the allowlist check, without the CAMEL-17751 fallback that re-reads `CamelHttpQuery`. - Without a secret the behaviour is unchanged, and the service logs a warning at startup that the callbacks are not signed. A secret that is set but blank, for example from an empty environment variable, fails the start of the service. - `LRASagaService.registerStep()` rejects a saga option whose name, in any case, is one of the parameters of the callback URLs (`Camel-Saga-Compensate`, `Camel-Saga-Complete`, `Camel-Saga-Signature`, `Camel-Saga-Lra`): the option would be in the URL twice, so the callback could not be read back. - `callbacksecret` is added to the extra sensitive keys of `UpdateSensitizeHelper` (the option is not in a component JSON, so the scan cannot find it), so camel-main masks the value when it logs its configuration. `SensitiveUtils` is regenerated. - Docs: a "Signing the participant callbacks" section in the Saga EIP page, with a pointer from the LRA page. It also says that, without a secret, the participant endpoints are meant to be reachable by the LRA coordinator only, and that a signed URL can still be called again for its own LRA. Upgrade-guide notes in the 4.24, 4.22.2 and 4.18.6 sections on main, for the startup warning and for the saga option names that now fail route creation. ## Tests - `LRACallbackSignerTest`: the signature is bound to the parameters, the callback path and the secret; it does not depend on the parameter order; a null option value signs as an empty one; missing parameters or a missing signature never match. - `LRASagaServiceAllowlistTest`: a saga option named like a callback URL parameter, in any case, is rejected at registration. - `LRAClientTest`: `join()` signs both callback URLs, with the LRA, when a secret is set, and leaves them unsigned otherwise. - `LRASagaServiceTest`: a blank secret fails the start of the service. - `LRASagaRoutesCallbackSignatureTest` (HTTP, camel-undertow): signed callbacks are routed with the signed values; a missing or changed signature, and a compensate signature used on `/complete`, get 403 and do not reach the step endpoint; signed values take precedence over request headers; the step gets the signed LRA, not the one named by the request; a URL re-pointed at another LRA, or signed without one, gets 403; the signature does not reach the step route; the raw query is read before a decoded `CamelHttpQuery`; a signed callback for a step without URIs does not take them from a decoded query; the URI allowlist still applies. - `LRASagaRoutesUnsignedCallbackTest` (HTTP, no Docker): without a secret, the callbacks are routed as before, and the URI allowlist still applies. - `LRASignedCallbacksIT`: runs the `LRAOptionsIT` tests with a secret, against the LRA coordinator container, so the coordinator calls back signed URLs. The whole `LRA*IT` suite (15 tests) is green locally. The branch is rebased on main after #27725 (CAMEL-25584), so the service and `camel.lra.*` carry both `allowedCoordinatorUrls` and `callbackSecret`, and the upgrade notes of both sit next to each other. ## Follow-ups - camel-spring-boot: `camel-lra-starter` sets the LRA service options one by one in `LraServiceAutoConfiguration`, so it needs the new option, tracked in [CAMEL-25590](https://issues.apache.org/jira/browse/CAMEL-25590). - Backports to `camel-4.22.x` and `camel-4.18.x` after this is merged. On 4.18.x the port needs adapting (`@Metadata(secret = true)`, and the LRA code there predates the URL-encoding of the callback parameters). _Claude Code on behalf of oscerd_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
