Deploy in Istio

This guide installs the Mitrity Mesh Authorizer into an Istio service mesh and scopes which namespaces and workloads it governs. The Mesh Authorizer runs as its own workload that makes the governance decision, plus a small set of Istio resources that route governed traffic to it for evaluation. For the concept and the identity model, start with Mesh Enforcement (Istio).

System Requirements

The Mitrity Mesh Authorizer runs as a workload in your cluster and is inline on the request path of every governed call. Provision it like a latency-sensitive control component, not a sidecar.

ResourceMinimumRecommended
Architecture64-bit (amd64 or arm64)amd64
Memory128 MB256 MB
CPU0.25 vCPU0.5–1 vCPU
Replicas12+ (with a PodDisruptionBudget)
NetworkOutbound HTTPS to the control planeOutbound HTTPS to the control plane, low-latency to governed workloads

Run at least two replicas with a PodDisruptionBudget in production so a single pod failure does not take down a fail-closed governed path (see Failure modes).

Prerequisites and version compatibility

Read this section before you install. The compatibility numbers below are honest about the difference between the API floor (what can technically register the Authorizer) and what MITRITY actually tests.

Istio and Kubernetes versions

VersionWhy
Tested / validatedIstio 1.24 on Kubernetes 1.29The in-mesh and egress end-to-end suites run green here. This is the version MITRITY stands behind.
Required API floorIstio ≥ 1.20The mesh-wide extensionProviders mechanism the Authorizer relies on is generally available from Istio 1.20. Older Istio cannot register the Authorizer at all.
Kubernetes floor≥ 1.26The Kubernetes baseline Istio 1.20–1.24 supports.

Istio 1.20–1.23 and ≥ 1.25 are expected to work (the same extensionProviders API surface) but are not part of the test matrix. Validate them in staging before you rely on them. Do not treat the floor as a blanket "supports 1.20+".

Required mesh configuration

  • Sidecar mode only. The Authorizer assumes a per-workload Envoy sidecar runs the authorization filter on the destination's inbound path and supplies the SPIFFE identity. Istio ambient mesh (ztunnel + waypoints) is unsupported -- run governed namespaces in sidecar mode.
  • STRICT mTLS on every governed namespace. Envoy only populates the caller's SPIFFE identity (source.principal) when the request arrived over mTLS. Under PERMISSIVE, the Authorizer sees an empty identity and fails closed (deny). The chart pins STRICT on the governed namespace by default. STRICT is not optional.
  • A mesh-trust-rooted server certificate for the Authorizer. Use cert-manager (recommended together with istio-csr) so the Authorizer's server cert chains to the mesh trust domain, or bring your own TLS Secret. See Transport security.
  • Control-plane reachability. The Authorizer calls the MITRITY control plane (https://api.mitrity.com by default) for policy sync, agent-to-agent (A2A) delegation evaluation, and credential brokering. It is not sidecar-injected, so make sure its namespace allows that outbound HTTPS.

Managed and distribution Istio

Registering the extension provider edits mesh-wide config, which requires cluster-admin and triggers an istiod rollout. Managed Istio offerings restrict or template that config, so you cannot always apply a raw IstioOperator:

  • GKE -- Cloud Service Mesh (managed): set the extension provider via the fleet / ControlPlaneRevision mesh-config overlay, not a free-form istioctl install.
  • EKS / self-managed: full mesh-config access if you run upstream Istio yourself; otherwise follow your managed add-on's mesh-config CRD.
  • AKS -- Istio add-on: customize mesh config through the add-on's supported ConfigMap / CRD surface.
  • OpenShift Service Mesh: set the extension provider on the ServiceMeshControlPlane (or Istio CR in OSSM 3.x), not via istioctl install.

The content of the registration is identical across distributions -- only the mechanism to apply it differs. Use your distribution's documented mesh-config path and confirm an istiod rollout completed.

Step 1 -- install the Mitrity Mesh Authorizer

Install from the published OCI chart on GHCR (the recommended path). Each MITRITY release publishes the chart as an OCI artifact, versioned to the release tag (vX.Y.Z → chart version X.Y.Z). The published chart's default image resolves to the image that same release published, so no image override is needed:

helm install mitrity-mesh-authorizer \
  oci://ghcr.io/mitrity-io/charts/mitrity-extauthz --version 0.12.0 \
  --namespace mitrity-system --create-namespace \
  --set extAuthz.apiKey=$MITRITY_API_KEY \
  --set extAuthz.agentId=mesh-authorizer-prod-001 \
  --set extAuthz.a2aNamespace=agents \
  --set istio.authorizationPolicy.namespace=agents \
  --set tls.certManager.issuerRef.name=istio-system

Replace 0.12.0 with the release version you are installing. Install the Authorizer in its own namespace (e.g. mitrity-system, injection off) and govern a different namespace (e.g. agents) so it does not govern itself.

Prefer to manage the control-plane API key yourself? Create an Opaque Secret with the key api-key and pass --set extAuthz.existingSecret=<name> instead of extAuthz.apiKey.

Verify the chart's signature before installing -- see Verifying signatures.

Step 2: register the extension provider

The Authorizer must be registered as an Istio authorization provider. This is mesh-wide configuration, not a namespaced manifest -- it lives in Istio's mesh config (meshConfig), which is global to the mesh and cannot be set by a namespaced Helm release. So you apply it once to your Istio install, separately from the chart.

The chart prints the exact snippet to apply in its install notes:

helm get notes mitrity-mesh-authorizer -n mitrity-system

Apply the printed registration to your Istio control plane (the mechanism depends on your distribution -- see Managed and distribution Istio). The registration points Istio at the Authorizer's Service FQDN and port, and sets two operator choices you should make deliberately:

  • maxRequestBytes -- how much of the request body Istio forwards to the Authorizer for inspection. See Sizing max request bytes.
  • failOpen -- what Istio does if the Authorizer is unreachable. See Failure modes. The chart's default is fail-closed.

After applying, restart the governed-namespace workloads so their sidecars reload the mesh config.

Step 3 -- scope which namespaces and workloads are governed

The chart renders a CUSTOM authorization policy and a STRICT mTLS policy into the governed namespace. Parameterize what gets governed:

ValueEffect
istio.authorizationPolicy.namespacenamespace whose traffic is governed (default: release namespace)
istio.authorizationPolicy.selectormatchLabels to govern specific workloads only (empty = the whole namespace)
istio.authorizationPolicy.rulesmatch rules to narrow by paths / methods / ports
istio.peerAuthentication.namespacenamespace STRICT mTLS is pinned on (default: governed namespace)
istio.extensionProviderNamemust equal the provider name you registered in Step 2

Istio's CUSTOM authorization rules cannot match on the caller's SPIFFE identity or a JWT -- that is an Istio constraint, and exactly why the Mitrity Mesh Authorizer keeps identity logic inside itself. Here you select which workloads, ports, and paths are in scope; the Authorizer reads the SPIFFE identity, maps it to the bound agent, and applies that agent's policy. See Per-Agent Identity in the Mesh for the binding.

Sizing max request bytes

The Authorizer parses the request body to extract the tool name, operation, and arguments (for operation matching and DLP). Istio forwards only up to maxRequestBytes of the body. If a body exceeds the cap, Istio truncates it (it does not reject it), and a truncated body can miss DLP content or fail operation matching.

  • Default: 64 KiB (65536) -- covers typical tool-call payloads.
  • Raise it if your agents send large arguments (file contents, long prompts). Measure real captured bodies and set the cap above your p99.
  • This value lives in the mesh-wide registration from Step 2, not in the chart's rendered resources -- update it there and reapply.

Transport security

The Authorizer makes the governance decision and is deliberately not sidecar-injected: it must not govern itself, and its control-plane traffic must not be intercepted by its own policy. Because it has no sidecar, the Envoy → Authorizer call cannot ride Istio mesh mTLS, so the Authorizer terminates its own TLS:

  • Default (recommended): cert-manager issues the server cert. With cert-manager + istio-csr, the issued cert chains to the same mesh root Envoy trusts, so Envoy validates the Authorizer's server name and (for mTLS) presents its own client cert. The default tls.requireClientCert=true makes the Authorizer require and verify the data plane's client cert, so only the trusted Envoy fleet can reach the governance gate.
  • Bring-your-own: set tls.existingTlsSecret to a pre-created kubernetes.io/tls Secret (and a separate client CA secret if needed).

The wire is encrypted on a routable Service by default (tls.enabled=true). The binary refuses to open a plaintext listener on a non-loopback address.

Do not strip the writable mounts. The credential broker persists a per-pod transport key to a writable volume the chart mounts for exactly this. If you tighten securityContext and remove those mounts, credential injection silently disables itself. Leave them in place.

Verifying enforcement

A quick allow + deny check from a governed workload confirms the Authorizer is in the path:

# In-scope for the calling agent's mission -> ALLOW (2xx).
kubectl exec -n agents deploy/<some-agent> -c <app> -- \
  curl -s -o /dev/null -w '%{http_code}\n' \
  http://<governed-tool>.tools.svc.cluster.local/<allowed-path>

# Outside the agent's mission scope (or a DLP-block / threat hit) -> DENY (403).
kubectl exec -n agents deploy/<some-agent> -c <app> -- \
  curl -s -D - -o /dev/null \
  http://<governed-tool>.tools.svc.cluster.local/<out-of-scope-path>

Watch the decision trace (metadata only -- never body content or credential values):

kubectl logs -l app.kubernetes.io/name=mitrity-extauthz -n mitrity-system -f \
  | grep '"ext_authz decision"'

If every request denies, the usual cause is missing STRICT mTLS -- with no mTLS there is no SPIFFE identity, so the Authorizer fails closed. Confirm STRICT mTLS is in force for the governed namespace. See Per-Agent Identity in the Mesh.

Failure modes

A governance gate's failure behavior is a deliberate operator choice. There are three independent knobs -- decide each one explicitly:

  1. Absent or unmapped identity → the Authorizer fails CLOSED (deny), by default. If the SPIFFE identity is missing (no mTLS), or an A2A hop drops the delegation-chain header (a downgrade-bypass attempt), or a valid identity does not resolve to a bound agent, the Authorizer cannot govern the request against a declared mission, so it denies. This is the secure default. An observe-only rollout can opt into a permissive behavior for resolved-but-unmapped identities; for production, leave it fail-closed. See Per-Agent Identity in the Mesh.

  2. Authorizer slow / down → Istio's failOpen decides, and it is your call. If the Authorizer is unreachable or times out, Istio (not the Authorizer) decides allow vs. deny per the failOpen flag on the registration:

    • failOpen: false (the default) → fail CLOSED: governed traffic is denied while the Authorizer is unavailable. Safest posture; run ≥ 2 replicas + a PodDisruptionBudget so a single failure does not trip it.
    • failOpen: true → fail OPEN: governed traffic flows ungoverned while the Authorizer is down -- availability over enforcement. Only choose this if an enforcement gap on that path is acceptable.
  3. Body over maxRequestBytes → truncated, not rejected. Past the cap, Istio truncates the body rather than returning an error. A truncated body can miss DLP content or fail operation matching -- size the cap above your real p99 body. See Sizing max request bytes.

Control-plane compatibility

The Mesh Authorizer calls the MITRITY control plane for policy / mission-profile sync, A2A delegation evaluation, and credential brokering. There is no runtime version handshake between the Authorizer and the backend today, so the safe pairing is explicit:

Run matching minor versions. Pair the Mesh Authorizer with a control-plane / backend release of the same minor. Upgrade the backend first, then the Authorizer, both within a minor. Skewing the Authorizer ahead of the backend can mean it calls an endpoint or field the backend does not yet serve; skewing it behind can miss enforcement the backend expects.

Performance

The Authorizer is inline on the synchronous request path of every governed call, so its decision cost is added to each request's latency. Treat the numbers below as added decision cost -- not total request latency -- and re-measure on your own node class.

A representative local benchmark of the full decision path (Apple M3 Pro, single-threaded) is on the order of tens of microseconds per request (mean ~56 µs, p95 ~61 µs), dropping further under concurrency as the gRPC dispatch parallelizes. These figures are hardware-specific and exclude the trained ONNX model kernel (which is stubbed in the portable benchmark) -- add your measured kernel time, which the intent-engine spec puts at sub-millisecond on CPU, for the full per-request budget.

Separately, the Envoy → Authorizer network hop is environment-dependent (topology, mTLS handshake amortized by Envoy's connection pool, replica placement). Budget it from your own cluster's intra-cluster gRPC p99, and keep Authorizer replicas close to the governed workloads (anti-affinity / topology-aware routing). A2A hops add one control-plane round trip on top.

Verifying signatures

Every MITRITY release cosign keyless-signs all images and the Helm chart OCI artifact, and attaches an SBOM and SLSA build provenance to each image. Signing uses GitHub OIDC -- there is no long-lived key.

Verify the Helm chart before installing (substitute the version you are installing):

cosign verify \
  --certificate-identity-regexp '^https://github.com/mitrity-io/iag-sentinel/\.github/workflows/release\.yml@refs/tags/v.*$' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
  ghcr.io/mitrity-io/charts/mitrity-extauthz:0.12.0

Verify the image before you run it:

cosign verify \
  --certificate-identity-regexp '^https://github.com/mitrity-io/iag-sentinel/\.github/workflows/release\.yml@refs/tags/v.*$' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
  ghcr.io/mitrity-io/mitrity-extauthz:v0.12.0

Inspect the attached SBOM and provenance:

cosign download sbom ghcr.io/mitrity-io/mitrity-extauthz:v0.12.0
cosign verify-attestation --type slsaprovenance \
  --certificate-identity-regexp '^https://github.com/mitrity-io/iag-sentinel/.*$' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
  ghcr.io/mitrity-io/mitrity-extauthz:v0.12.0

Related Documentation

Deploy in Istio — Documentation | MITRITY