New: Agent approvals, red teaming and model scanning. See how
MITRITY
Docs / Gateway, Sidecar & Mesh / Deploy in Istio

Deploy in Istio

This guide shows you how to install the Mitrity Mesh Authorizer into an Istio service mesh, and how to choose which namespaces and workloads it governs.

The installation has two parts:

  • the Mesh Authorizer itself, a workload of its own that makes the governance decision;
  • 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, inline on the request path of every governed call. Provision it like a latency-sensitive control component, not like 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

In production, run at least two replicas with a PodDisruptionBudget, so that 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 separate the API floor (what can technically register the Authorizer) from the versions 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 that 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 that Istio 1.20–1.24 supports.

Istio 1.20–1.23 and ≥ 1.25 are expected to work, because they have the same extensionProviders API surface. But they 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 that 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 that 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 that its namespace allows that outbound HTTPS.

Managed and distribution Istio

Registering the extension provider edits mesh-wide config. That 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 with Cloud Service Mesh (managed): set the extension provider through the fleet / ControlPlaneRevision mesh-config overlay, not a free-form istioctl install.
  • EKS / self-managed: you have full mesh-config access if you run upstream Istio yourself. Otherwise, follow your managed add-on's mesh-config CRD.
  • AKS with the 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 the Istio CR in OSSM 3.x), not through istioctl install.

The content of the registration is identical across distributions. Only the way you apply it differs. Use your distribution's documented mesh-config path, and confirm that an istiod rollout completed.

Step 1: install the Mitrity Mesh Authorizer

Install from the published OCI chart on GHCR, which is 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 the same release published, so you need no image override:

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 (for example mitrity-system, with injection off), and govern a different namespace (for example agents), so that it does not govern itself.

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 you install it. 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 a namespaced Helm release cannot set it. 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. It also sets two operator choices that 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 you apply it, restart the governed-namespace workloads, so that 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. These values set 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 that 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 on a JWT. That is an Istio constraint, and it is 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. A truncated body can miss DLP content or fail operation matching.

  • Default: 64 KiB (65536). This covers typical tool-call payloads.
  • Raise it if your agents send large arguments, such as file contents or 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 it 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 that Envoy trusts. 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, which the chart mounts for exactly this purpose. 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 that 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. It holds 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 is denied, the usual cause is missing STRICT mTLS. With no mTLS, there is no SPIFFE identity, so the Authorizer fails closed. Confirm that STRICT mTLS is in force for the governed namespace. See Per-Agent Identity in the Mesh.

Failure modes

How a governance gate fails is a deliberate operator choice. There are three independent knobs, and you should decide each one explicitly:

  1. Absent or unmapped identity → the Authorizer fails CLOSED (deny), by default. The Authorizer denies the request when:

    • the SPIFFE identity is missing (no mTLS);
    • an A2A hop drops the delegation-chain header (a downgrade-bypass attempt);
    • a valid identity does not resolve to a bound agent.

    In each case, the Authorizer cannot govern the request against a declared mission. 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 between allow and deny, based on the failOpen flag on the registration:

    • failOpen: false (the default) → fail CLOSED: governed traffic is denied while the Authorizer is unavailable. This is the safest posture. Run ≥ 2 replicas + a PodDisruptionBudget, so that a single failure does not trip it.
    • failOpen: true → fail OPEN: governed traffic flows ungoverned while the Authorizer is down. This puts availability over enforcement. Only choose it 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, so 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 you pair the versions explicitly:

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. An Authorizer ahead of the backend can call an endpoint or field that the backend does not serve yet. An Authorizer behind the backend can miss enforcement that 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. The cost drops further under concurrency, as the gRPC dispatch parallelizes.

These figures are hardware-specific, and they exclude the trained ONNX model kernel, which is stubbed in the portable benchmark. For the full per-request budget, add the kernel time that you measure on your own nodes.

Separately, the Envoy → Authorizer network hop depends on your environment: the topology, the mTLS handshake (amortized by Envoy's connection pool) and the replica placement. Budget it from your own cluster's intra-cluster gRPC p99. 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 signs all images and the Helm chart OCI artifact with cosign, keylessly. It also attaches an SBOM and SLSA build provenance to each image. Signing uses GitHub OIDC, so there is no long-lived key.

Verify the Helm chart before you install it (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