Chio/Docs

Envoy ext_authz

Envoy's external authorization filter is the highest-leverage network integration chio offers. One adapter makes chio available to every service behind an Envoy-based data plane: Istio, Consul Connect, Gloo, AWS App Mesh, Cilium, and standalone Envoy. It requires no new proxy, no language-specific SDK, no code change in the backing service. The mesh calls chio before every request, chio answers allow or deny, and a receipt is signed and attached in the same round trip. Latency budget: under 5ms at p50 on localhost.


Why This Integration Is Critical

Every other chio integration targets a single runtime, framework, or language. ext_authz is different: it is a multiplier. A single adapter puts chio into every service in every mesh that runs Envoy as the data plane, regardless of the service's language, deployment model, or framework. Istio uses ext_authz natively via AuthorizationPolicy CUSTOM actions; Consul Connect via envoy_extensions.ext_authz; AWS App Mesh, Gloo, standalone Envoy, and Cilium's L7 policy layer all support the same filter. Approximately 80% of enterprise service meshes run Envoy, so the reach is enormous.

The protocol fit is almost exact. Envoy expects the external service to accept a request, return allow or deny, and optionally inject headers. chio's evaluation engine already produces exactly this shape, so adoption becomes a configuration change rather than an architecture change. There is no "another sidecar" objection, because the proxy is already running.


The ext_authz Protocol

Envoy's external authorization filter intercepts every request (or a configured subset) and sends a check request to an external service before forwarding upstream. That service returns allow or deny, with optional header mutations applied to the request going upstream or to the response going back to the client.

rendering…
Envoy's ext_authz filter calls chio for every request, receives allow/deny plus header mutations, and forwards (or rejects) accordingly.

Two Transport Modes

ext_authz can talk to the external service over gRPC or HTTP. Today chio-envoy-ext-authz ships the gRPC path (HTTP/2, binary protobuf) via the generated envoy.service.auth.v3.Authorization service. A native HTTP-mode adapter is not yet shipped; see HTTP Mode Adapter below for detail.


Envoy Filter Configuration

A minimum gRPC-mode filter chain wiring chio into Envoy:

yaml
http_filters:
  - name: envoy.filters.http.ext_authz
    typed_config:
      "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
      grpc_service:
        envoy_grpc:
          cluster_name: chio_ext_authz
        timeout: 0.25s
      with_request_body:
        max_request_bytes:     8192
        allow_partial_message: true
        pack_as_bytes:         true
      allowed_headers:
        patterns:
          - exact:  authorization
          - prefix: x-chio-
      # Fail-closed: deny if chio is unreachable
      failure_mode_allow: false
      include_peer_certificate: true

clusters:
  - name: chio_ext_authz
    type: STRICT_DNS
    lb_policy: ROUND_ROBIN
    load_assignment:
      cluster_name: chio_ext_authz
      endpoints:
        - lb_endpoints:
            - endpoint:
                address:
                  socket_address: { address: 127.0.0.1, port_value: 9091 }

Fail-closed is the default

If Envoy cannot reach chio, the request is denied. That is the safe default for capability enforcement. Change failure_mode_allow to true only for non-security-critical workloads where availability outweighs enforcement.

Mapping ext_authz to chio

The ext_authz CheckRequest carries the downstream request's attributes. The adapter projects these onto chio's protocol-agnostic request model: method and path map directly; Authorization and x-chio-capability headers produce caller identity and capability id; the request body is hashed to SHA-256 and never stored in full; the source principal (mTLS peer certificate or SPIFFE ID) becomes the caller subject; Envoy's request id flows through as request_id for correlation.


Capability Token Transport

Capability tokens travel in one of two headers. The adapter checks them in order:

  1. x-chio-capability (preferred, explicit): raw capability token id issued by chio.
  2. Authorization: Bearer <token>: standard OAuth2 flow, useful when the caller is already using bearer tokens for other purposes.
  3. If neither is present, the request is evaluated under AuthMethod::Anonymous. Your policy decides whether anonymous callers are allowed.

The adapter never forwards raw tokens upstream. The upstream service sees the receipt id and a hash of the token, consistent with chio's never-store-secrets policy.


Verdict to CheckResponse

text
chio Verdict::Allow
  -> CheckResponse { status: OK (0) }
     + OkHttpResponse {
         headers: [
           { "x-chio-receipt-id":  "<receipt-id>" },
           { "x-chio-policy-hash": "<policy-hash>" },
         ]
       }

chio Verdict::Deny { reason, guard, http_status }
  -> CheckResponse { status: PermissionDenied (7) }
     + DeniedHttpResponse {
         status:  StatusCode(<http_status>),   # typically 403
         headers: [
           { "x-chio-receipt-id":      "<receipt-id>" },
           { "x-chio-denial-reason":   "<reason>" },
           { "x-chio-denial-guard":    "<guard>" },
         ],
         body: "<structured JSON error>"
       }

chio Verdict::Cancel / Incomplete
  -> CheckResponse { status: Unavailable (14) }
     (Envoy applies failure_mode_allow policy)

On allow, the upstream service receives the receipt id as a header, so it can correlate its own logs with the chio receipt log without calling chio directly. On deny, the receipt id is set on the response returned to the client, which the client can reference in an appeal or support request.


gRPC Adapter

The adapter implements envoy.service.auth.v3.Authorization/Check as a thin shim over a pluggable kernel trait. The production crate is chio-envoy-ext-authz. It exposes one service type and one trait:

rust
// From crates/chio-envoy-ext-authz/src/service.rs

/// Kernel abstraction. Implementations delegate to chio-kernel, to
/// HttpAuthority in chio-http-core, or to a test stub.
#[async_trait]
pub trait EnvoyKernel: Send + Sync + 'static {
    async fn evaluate(
        &self,
        request: ToolCallRequest,
    ) -> Result<Verdict, KernelError>;
}

/// Generic over the kernel implementation. Each CheckRequest is
/// translated to a ToolCallRequest, handed to K::evaluate, and the
/// returned Verdict is mapped back onto a compliant CheckResponse.
pub struct ChioExtAuthzService<K: EnvoyKernel> {
    kernel: K,
}

impl<K: EnvoyKernel> ChioExtAuthzService<K> {
    pub fn new(kernel: K) -> Self { Self { kernel } }
}

#[async_trait]
impl<K: EnvoyKernel> Authorization for ChioExtAuthzService<K> {
    async fn check(
        &self,
        request: Request<CheckRequest>,
    ) -> Result<Response<CheckResponse>, Status> { /* ... */ }
}

A minimal binding. Any type that implements EnvoyKernel plugs in; for production, write a small adapter that delegates to chio-kernel or to HttpAuthority in chio-http-core.

rust
use async_trait::async_trait;
use chio_envoy_ext_authz::{
    proto::envoy::service::auth::v3::authorization_server::AuthorizationServer,
    translate::{ToolCallRequest, Verdict},
    ChioExtAuthzService, EnvoyKernel, KernelError,
};

struct MyKernel;

#[async_trait]
impl EnvoyKernel for MyKernel {
    async fn evaluate(
        &self,
        _request: ToolCallRequest,
    ) -> Result<Verdict, KernelError> {
        // Delegate to chio-kernel / HttpAuthority / custom policy here.
        Ok(Verdict::Allow)
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let svc = ChioExtAuthzService::new(MyKernel);
    tonic::transport::Server::builder()
        .add_service(AuthorizationServer::new(svc))
        .serve("0.0.0.0:9091".parse()?)
        .await?;
    Ok(())
}

HTTP Mode Adapter

HTTP mode is not yet shipped

Envoy ext_authz supports an HTTP-mode transport in addition to gRPC. chio does not expose a native HTTP ext_authz endpoint today. The sidecar ships /chio/evaluate, /chio/verify, /chio/health, approval admin routes, and the capability mint/release surface. There is no /ext_authz endpoint, and the evaluate endpoint does not translate Envoy's HTTP-mode request conventions. Until an HTTP-mode adapter ships, use the gRPC adapter above; an Envoy config pointed at a non-existent HTTP endpoint will fail closed on every request.

Istio Integration

Istio is the most common Envoy-based mesh. chio plugs into Istio via AuthorizationPolicy with the CUSTOM action, which delegates the decision to an ext_authz provider.

chio layers on top of Istio's RBAC. Istio answers "can service A talk to service B." chio answers "does this agent have a valid capability token for this specific tool invocation, within budget, and passing all guards." Istio RBAC covers service identity (via mTLS and SPIFFE IDs) and coarse allow/deny at the path level. chio covers per-tool capability, signed receipts, the guard pipeline (WASM, Rego, built-in), and per-capability budget limits, none of which Istio native RBAC models.

Register chio as an ext_authz Provider

yaml
# istio-configmap or IstioOperator overlay
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  meshConfig:
    extensionProviders:
      - name: chio-ext-authz
        envoyExtAuthzGrpc:
          service: chio-ext-authz.chio-system.svc.cluster.local
          port:    9091
          timeout: 0.25s
          includeRequestHeadersInCheck:
            - authorization
            - x-chio-capability
            - x-chio-session-id
            - x-request-id

Route Traffic to chio

yaml
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name:      chio-tool-authorization
  namespace: agent-tools
spec:
  # Apply only to opted-in tool server workloads
  selector:
    matchLabels:
      chio.protocol/secured: "true"
  action: CUSTOM
  provider:
    name: chio-ext-authz
  rules:
    - to:
        - operation:
            # Guard pipeline handles fine-grained decisions
            paths: ["/*"]

Not every service needs chio. The chio.protocol/secured: "true" label gates which workloads go through chio evaluation. Services without the label fall back to standard Istio RBAC only.


SPIFFE / SPIRE Workload Identity

In a mesh that issues SPIFFE IDs via SPIRE, the source principal in the ext_authz CheckRequest carries the SPIFFE ID. The chio adapter lifts it into the normalized workloadIdentity record so policy and receipts bind to the workload, not the transport credential. For the full shape, the three credential kinds, and how this interacts with runtime-assurance tiers, see the Workload Identity concept. The operational recipe for gating tools on a SPIFFE ID lives in the Bind Workload Identity guide.


Consul Connect

Consul Connect uses Envoy as its data plane, so ext_authz works the same way. The configuration surface differs (HCL instead of YAML, service-defaults instead of Istio CRDs), but the semantics are identical. Consul Intentions handle service-to-service authorization (L4 identity), while chio handles capability-level authorization (L7 tool policy). The two layer cleanly: Consul decides whether the agent-orchestrator can even talk to the code-execution tool, and chio decides whether the specific capability token it presents is valid for the invocation it is about to make.


Deployment Topologies

Three topologies are common in production. The right pick depends on resource budget, tenant isolation needs, and how much latency you can absorb.

Sidecar (per pod)

chio runs in the same pod as Envoy. ext_authz calls traverse loopback. Lowest latency, strongest isolation, highest resource footprint.

Cluster service

chio runs as a centralized Deployment behind a service. Every Envoy proxy calls it over the cluster network. Simplest operationally, slightly higher latency (1-5ms in-cluster).

DaemonSet (per node)

chio runs one instance per node. Envoy sidecars call the node-local instance. Good middle ground between the two extremes.

FactorSidecarCluster serviceDaemonSet
Latency<1ms1-5ms<1ms
Resource overheadHigh (per pod)Low (shared)Medium (per node)
Policy isolationPer-podCluster-widePer-node
Failure blast radiusSingle podAll podsAll pods on node
Best forHigh-security, multi-tenantDev/staging, low trafficProduction, single-tenant

Latency Budget

ComponentTargetNotes
Envoy filter overhead<0.1msIn-process, negligible
Network to chio<0.5msLoopback or node-local
chio evaluation<2msPolicy match plus guard pipeline
Receipt signing<0.5msEd25519, fast
Total ext_authz<3ms p99, <5ms totalOn localhost

Optimization levers: Envoy maintains persistent gRPC connections to chio so no per-request connection setup is paid; chio caches compiled policy in memory and hot-swaps on reload; guard results can be cached for idempotent guards keyed on capability token plus route; receipt signing is synchronous but receipt persistence is async, so disk I/O does not block the response.


Migration Paths

From No Auth to chio

  1. Deploy chio as a cluster-wide service in the chio-system namespace.
  2. Register chio as an ext_authz provider in the mesh config.
  3. Apply the AuthorizationPolicy CUSTOM rule to a single test workload.
  4. Verify receipts are produced and allow/deny behavior is correct.
  5. Expand via label selectors one workload at a time.

From OPA to chio

Organizations already using Open Policy Agent with ext_authz can migrate incrementally. Run chio alongside OPA as a second provider, port policies to chio guards (the Rego guard runs OPA policies natively), validate parity, then remove the OPA provider.

Shadow Mode

For risk-averse rollouts, chio supports shadow evaluation. Every request is evaluated and signed but always returns allow. Analyze the shadow receipts to validate policy before enforcing. Flip to enforcing mode with a configuration toggle once satisfied.


Next Steps

  • Receipt Dashboard · visualize the receipts produced by the ext_authz adapter
  • AWS Lambda · the serverless counterpart to the ext_authz sidecar model
  • Budgets · per-capability spending envelopes, enforced on every request