Skip to main content

Module tenant_resolver

Module tenant_resolver 

Source
Expand description

Tenant resolution for multi-tenant A2A servers.

TenantResolver extracts a tenant identifier from incoming requests, enabling per-tenant routing, configuration, and resource isolation.

§Built-in resolvers

ResolverStrategy
HeaderTenantResolverReads a configurable HTTP header (default: x-tenant-id)
BearerTokenTenantResolverExtracts Authorization: Bearer <token> and optionally maps it
PathSegmentTenantResolverExtracts a URL path segment by index

§Security: all three read what the caller sent

A header, a bearer token and a path segment are all client-controlled. Nothing in this module authenticates any of them, so a resolver on its own provides no isolation at all: anyone who can reach the server can send x-tenant-id: victim and be treated as victim.

RequestHandler::resolve_tenant states the model this is meant to sit inside — the resolver is the source of truth precisely because it reads “trusted request context (an auth token, a gateway-set header, a URL path segment)”. That word trusted is a precondition on the deployment, and it is the whole of the security argument. It was stated where the value is consumed and not here, where the resolver is chosen, which is the wrong way round: nobody picks a resolver by reading the handler.

For that precondition to hold, one of these has to be true:

  • a gateway or sidecar in front of this server strips the header from client traffic and sets it itself from an authenticated identity — the same discipline RateLimitConfig::trusted_proxy_hops applies to X-Forwarded-For, and for a stronger reason: this decides which tenant’s data you read, not merely whose quota you spend; or
  • the resolver derives the tenant from something the server has already verified — a JWT whose signature was checked, for example, via BearerTokenTenantResolver::with_mapper; or
  • the deployment has exactly one tenant and this is routing, not isolation.

Two more things worth knowing before relying on this:

  • A resolver returning None falls through to the shared default ("") partition. RequestHandlerBuilder::require_resolved_tenant turns that into a rejection instead, and is off by default for compatibility.
  • Ordering matters the same way it does for per-caller rate limiting: a resolver that reads something an interceptor is supposed to have established will read nothing if it runs first.

§Example

use a2a_protocol_server::tenant_resolver::HeaderTenantResolver;
use a2a_protocol_server::CallContext;

let resolver = HeaderTenantResolver::default();
let ctx = CallContext::new("message/send")
    .with_http_header("x-tenant-id", "acme-corp");

// resolver.resolve(&ctx) would return Some("acme-corp".into())

Structs§

BearerTokenTenantResolver
Extracts a tenant ID from the Authorization: Bearer <token> header.
HeaderTenantResolver
Extracts a tenant ID from a configurable HTTP header.
PathSegmentTenantResolver
Extracts a tenant ID from a URL path segment by index.

Traits§

TenantResolver
Trait for extracting a tenant identifier from incoming requests.