Andrea Cosentino created CAMEL-25028:
----------------------------------------

             Summary: camel-openfga: new component for relationship-based 
(Zanzibar/ReBAC) authorization decisions
                 Key: CAMEL-25028
                 URL: https://issues.apache.org/jira/browse/CAMEL-25028
             Project: Camel
          Issue Type: New Feature
            Reporter: Andrea Cosentino


h2. Why

Camel now has two of the three pieces of a modern zero-trust authorization 
story:

* {{camel-spiffe}} answers *who is the caller* (workload identity, X.509-SVID / 
JWT-SVID).
* {{camel-opa}} answers *what do the rules say* (policy-as-code, Rego, 
evaluated per Exchange).

The missing third piece is *what is this subject's relationship to this 
resource*. Policy-as-code is
a poor fit for that question: expressing "anne can read document:budget because 
she owns the folder
it lives in" in Rego means shipping the whole relationship graph into the 
policy input, which does
not scale past a handful of tuples.

[OpenFGA|https://openfga.dev] (CNCF, an implementation of Google's Zanzibar 
paper) is the widely
adopted answer: relationship tuples {{(user, relation, object)}} are stored in 
the decision point, and
the route asks it a question instead of shipping it data. Today a Camel route 
that needs this has to
hand-roll an SDK call in a {{.process()}} block, which is exactly the situation 
{{camel-opa}} was
created to end -- and hand-rolled authorization code is where the interesting 
mistakes live.

h2. What

A new {{camel-openfga}} component, deliberately built as a companion to 
{{camel-opa}} and sharing its
shape and its security posture, so an operator who knows one knows the other.

*Producer operations* (URI: {{openfga:operation}}):

|| Operation || Purpose ||
| {{check}} | the authorization decision: may this subject use this relation on 
this object |
| {{batchCheck}} | many checks in one round trip, for filtering a collection |
| {{listObjects}} | which objects of a type this subject may reach through a 
relation |
| {{listRelations}} | which of a set of relations this subject has on one 
object |
| {{listUsers}} | which subjects hold a relation on one object |
| {{writeTuples}} | grant: add relationship tuples (a route that creates a 
resource also grants access to it) |
| {{deleteTuples}} | revoke: remove relationship tuples |

*Route enforcement*: an {{OpenFgaSecurityPolicy}} implementing 
{{AuthorizationPolicy}}, so a route is
guarded declaratively rather than by an {{if}} in a processor -- the direct 
counterpart of
{{OpaSecurityPolicy}}:

{code:java}
from("platform-http:/orders")
    .policy(openFgaPolicy)     // a deny throws CamelAuthorizationException
    .to("direct:handleOrder");
{code}

h2. Security posture

Carried over from {{camel-opa}}, because the same class of mistake applies:

* *Fails closed.* An unreachable or erroring OpenFGA server denies. 
{{failOpen}} exists, is off by
  default and is annotated {{security = "insecure:dev"}}.
* *The question is not selectable by the message.* {{storeId}}, 
{{authorizationModelId}} and
  {{relation}} come from the endpoint only. An inbound message cannot downgrade 
the relation it is
  judged against (e.g. {{owner}} -> {{reader}}) or point the check at a 
different store or an older
  model revision.
* *Decision headers are cleared on entry*, written on every evaluation, and 
never read back as input,
  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 an evaluation failure.* If the configured 
{{user}} or {{object}}
  expression resolves to blank, the exchange is denied and {{failOpen}} does 
*not* apply: nothing
  failed at the decision point, the request simply carried no identity. Without 
this split,
  {{failOpen}} would turn "no identity" into "allowed".
* *A wildcard subject is rejected.* Verified against OpenFGA v1.21.0: 
{{check(user:*, reader,
  document:public)}} returns {{allowed: true}} whenever a public-read tuple 
exists. {{user:*}} is a
  legitimate *tuple* subject ("public") but never a legitimate *checking* 
subject, so a {{user}}
  expression resolving to a typed wildcard is denied rather than sent.
* *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.
* Secrets ({{apiToken}}, {{clientSecret}}) are marked {{secret = true}}; both 
already match
  {{SensitiveUtils}} keywords, so endpoint URIs are masked in logs, JMX and 
health output.
* {{sslContextParameters}} / {{useGlobalSslContextParameters}} for TLS, 
including presenting a client
  certificate to an OpenFGA server that requires mutual TLS -- a SPIFFE 
X.509-SVID, closing the loop
  with {{camel-spiffe}}.

h2. Deliverables

* {{components/camel-openfga}} -- component, endpoint, producer, configuration, 
constants, operations,
  {{security/OpenFgaSecurityPolicy}}.
* Producer and security-policy readiness health checks probing the server's 
{{/healthz}} endpoint
  (verified to return {{{"status":"SERVING"}}} with HTTP 200; the probe asserts 
the body, not only the
  status, so a server answering 200 while not serving is not reported UP).
* {{test-infra/camel-test-infra-openfga}} -- Testcontainers service on
  {{mirror.gcr.io/openfga/openfga}}. The image publishes {{amd64}} and 
{{arm64}} only, so
  {{skipITs.ppc64le}} and {{skipITs.s390x}} are set, as for {{camel-opa}}.
* Unit tests (mocked client) plus an end-to-end IT against a real OpenFGA 
server.
* Component documentation.

h2. Dependency

{{dev.openfga:openfga-sdk}} 0.10.1 (Apache-2.0). Transitively Jackson 2 
(already managed by Camel's
parent), {{org.openapitools:jackson-databind-nullable}} and 
{{io.opentelemetry:opentelemetry-api}}.
Unlike the OPA SDK, it applies default connect/read timeouts (10s each), 3 
retries, and reuses a
single {{HttpClient}}, so the component does not need to supply its own 
transport -- only to expose
the knobs and to bound the blocking wait so a routing thread can never park 
indefinitely.

h2. Follow-ups (separate issues once this lands)

* Contextual tuples and condition context on {{check}}, with an explicit opt-in 
and a reviewed trust model.
* {{expand}}, {{readTuples}} and {{readChanges}} operations.
* Store and authorization-model management operations.




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to