oscerd opened a new pull request, #26974:
URL: https://github.com/apache/camel/pull/26974

   Fixes [CAMEL-25028](https://issues.apache.org/jira/browse/CAMEL-25028).
   
   ## Why
   
   Camel has two of the three pieces of an authorization story: `camel-spiffe` 
establishes **who the caller is**, and
   `camel-opa` evaluates **what the rules say**. The missing piece is **what 
the caller's relationship to the resource
   is**. Policy-as-code is a poor fit for that question — expressing "anne may 
read `document:budget` because she owns the
   folder it lives in" in Rego means shipping the whole relationship graph into 
the policy input on every message — so
   routes that need it hand-roll an SDK call in a `.process()` block, which is 
the situation `camel-opa` was created to
   end.
   
   This adds `camel-openfga`, which delegates the question to 
[OpenFGA](https://openfga.dev) (CNCF, an implementation of
   Google's Zanzibar paper) and records the answer on the Exchange. It is 
deliberately built to `camel-opa`'s shape, so an
   operator who knows one knows the other.
   
   ## What
   
   **Producer** — `openfga:<operation>`:
   
   | Operation | Purpose |
   |---|---|
   | `check` | the authorization decision; verdict on `CamelOpenFgaAllowed`, 
body untouched |
   | `batchCheck` | filters the body's object identifiers down to the ones the 
check allowed |
   | `listObjects` | which objects of a type a subject can reach through a 
relation |
   | `listRelations` | which of a set of relations a subject holds on one 
object |
   | `listUsers` | which subjects hold a relation on one object |
   | `writeTuples` / `deleteTuples` | grant and revoke, so a route that creates 
a resource can grant access to it |
   
   **Route enforcement** — `OpenFgaSecurityPolicy`, an `AuthorizationPolicy`, 
the direct counterpart of
   `OpaSecurityPolicy`:
   
   ```java
   from("platform-http:/documents")
           .policy(openFgaPolicy)     // a deny throws 
CamelAuthorizationException
           .to("direct:serveDocument");
   ```
   
   `user`, `object` and `relation` are Simple expressions evaluated per 
Exchange, so a plain `to()` is enough — no `toD()`
   and the endpoint-cache churn it brings.
   
   ## Security posture
   
   - **Fails closed.** An unreachable or erroring server denies. `failOpen` is 
off by default and annotated
     `security = "insecure:dev"`. It applies only to `check` and the security 
policy; `batchCheck`/`listObjects` ignore it,
     because "proceed" for a filter would mean handing back the objects it 
never managed to filter.
   - **The question is not the message's to choose.** `storeId`, 
`authorizationModelId`, `relation` and the operation come
     from the endpoint only. An inbound message cannot point the check at 
another store, pin an older model revision,
     downgrade the relation demanded from `owner` to `reader`, or turn a check 
into a tuple write.
   - **Decision headers are cleared on entry**, before the expressions are 
evaluated, written on every evaluation, and
     never read back as inputs — so a verdict a message arrived with never 
survives, including down the paths that throw
     (the CAMEL-24754 lesson).
   - **A missing identity is a deny, not a failure.** If `user` or `object` 
resolves to blank — or to a bare `user:`
     prefix, which is what a configured prefix plus an unresolved expression 
leaves — the Exchange is denied and `failOpen`
     does not reach it. OpenFGA would answer HTTP 400, which *is* a failure, 
and `failOpen` would then read it as an allow.
   - **A wildcard subject is refused.** Verified against OpenFGA 1.21.0: 
`check(user:*, reader, document:public)` returns
     `allowed: true` wherever a public-access tuple exists. `user:*` is a 
legitimate *tuple* subject but never a legitimate
     *checking* subject, so a `user` expression resolving to a typed wildcard 
is denied rather than sent. The IT asserts
     both halves of this — that the server really would say yes, and that the 
component says no.
   - **No contextual tuples or condition context in this first cut.** A 
contextual tuple derived from a message is a
     self-authorization primitive (`(user:me, owner, document:secret)`), so it 
is left out entirely rather than shipped
     with a gate that has not been reviewed. Follow-up issue.
   - `apiToken` and `clientSecret` are marked secret and already match 
`SensitiveUtils` keywords, so no core change was
     needed for URI masking.
   - `sslContextParameters` / `useGlobalSslContextParameters` for TLS, 
including presenting a SPIFFE X.509-SVID to a
     server requiring mutual TLS — which closes the loop with `camel-spiffe`.
   
   ## Two things found while writing this
   
   - **`openfga-sdk` 0.10.1 accepts a `connectTimeout` and never reads it.** 
`getConnectTimeout()` is called nowhere in
     the jar, and the `HttpClient.Builder` the SDK uses by default sets none, 
so the connect phase would be bounded only by
     the OS. The component therefore supplies its own 
`ApiClient(HttpClient.Builder)` — which is also the only seam an
     `SSLContext` can go through, since it can only be set while an 
`HttpClient` is being built.
   - **`clientBatchCheck` does not preserve input order** — it fans out in 
parallel and returns completion order. The
     producer filters the request list against the allowed set instead of 
building the result from the responses, so the
     body comes back in the order the route asked in. Caught by the IT, which 
failed intermittently until it was fixed.
   
   Also: the probe requires the server to report `SERVING`, matched as a whole 
value, because
   `"NOT_SERVING".contains("SERVING")` is `true` — a unit test caught that 
before it could report an unhealthy server as
   ready.
   
   ## Testing
   
   - 77 unit tests with a mocked client, covering the verdict paths, every deny 
reason, stale-header clearing, the
     `failOpen` boundary, the identifier guards, and all seven operations.
   - 10 end-to-end integration tests against a real OpenFGA server via a new 
`camel-test-infra-openfga` module
     (`mirror.gcr.io/openfga/openfga`, which publishes `amd64` and `arm64` 
only, so `skipITs.ppc64le` and `skipITs.s390x`
     are set as for `camel-opa`). The store, model and starting tuples are 
created through OpenFGA's HTTP API, so what the
     component does is measured against a graph it did not build.
   - Documentation in `src/main/docs/openfga-component.adoc`.
   
   No upgrade-guide entry: this is a new component, and the upgrade guide is 
for migration only.
   
   One generated change may look out of place: the full reactor adds `apitoken` 
to `SensitiveUtils` (and to
   `sensitive-keys.json`) from this component's `secret` metadata, and 
re-indents the `SENSITIVE-PATTERN: END` marker
   while rewriting that block. Both come straight from the generator — 
excluding the re-indent would leave the tree
   dirty after any build and fail CI's uncommitted-changes check.
   
   ## Follow-ups
   
   Separate issues once this lands: contextual tuples and condition context on 
`check` with a reviewed trust model; the
   `expand`, `readTuples` and `readChanges` operations; store and 
authorization-model management.
   
   A third, unrelated one: `camel-opa`'s documentation and 
`OpaConfiguration.getIncludeProperties()` both state that
   `camel-keycloak` and `camel-oauth` "record the identity they verified as an 
exchange property". They do not —
   `camel-keycloak` has only a `CamelKeycloakSubjectToken` *header* and sets no 
exchange properties, and
   `CamelKeycloakSubject` does not exist anywhere in the codebase. This PR's 
docs say "a property your authentication step
   has to set" instead; `camel-opa`'s wording should be corrected separately.
   
   ---
   
   _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