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]

Reply via email to