Skip to content

Service Mesh integration

Once your gateways are running, the mesh adapter binds Aegis policies to specific services in your Kubernetes clusters — without asking your operators to learn a new mesh control plane.

Istio ambient is the first-class adapter; Linkerd, Consul, and Envoy adapters are also supported (some behind feature flags as they reach general availability — see the per-adapter notes below).

Why bother?

Aegis already enforces policy at gateway TLS termination (north-south traffic). The mesh adapter extends that enforcement to east-west traffic — service-to-service inside your cluster — using your existing service mesh's data plane.

You get:

  • mTLS by default for all bound namespaces (ambient ztunnel handles this with no per-pod overhead).
  • Per-service policy bindings that translate Aegis policies into the mesh's native CRDs. Auditors get a clean diff: "Aegis policy X → Istio CRDs Y, Z, W." No "trust the mesh" handwaving.
  • Drift detection — every five minutes the adapter walks the live cluster CRDs, hashes them canonically, and flags anything that no longer matches what Aegis applied. Drift surfaces on the dashboard; sustained drift triggers a SEV3 PagerDuty.
  • Standard rollback flow — mesh policy bindings flow through the same rollout engine as gateway policy. One audit trail, one rollback path.

Quick start (Istio ambient on k3d)

Five-minute end-to-end demo:

git clone https://github.com/velikey/aegis.git
cd aegis
./scripts/demo/mesh-istio-ambient.sh

What you get:

  1. A k3d cluster with Gateway API CRDs installed.
  2. Istio ambient mode installed (revision aegis).
  3. A sample httpbin service labeled into ambient.
  4. PeerAuthentication (STRICT mTLS) + DestinationRule (ISTIO_MUTUAL) applied with the same shape the IstioAdapter renderers emit.
  5. Assertions: STRICT mTLS landed; ztunnel pods are healthy.

The demo tears down the k3d cluster on exit.

Production install

# Register the cluster with Axis
aegis mesh init istio --cluster prod-us --mode ambient

This calls POST /api/mesh/adapters/istio/install, which:

  1. Validates the operator's kubeconfig is reachable.
  2. Runs istioctl install --set profile=ambient -y against the cluster (with revision aegis).
  3. Records the cluster as MeshCluster in the Axis DB.

Bind a policy to a service

aegis mesh bind \
  --cluster prod-us \
  --service payments \
  --namespace default \
  --policy pol_12345

The adapter generates three CRDs:

Aegis policy clause Istio resource Notes
algorithms.minTlsVersion = TLSv1.3 PeerAuthentication.mtls.mode = STRICT TLS-1.2-minimum bindings stay PERMISSIVE
algorithms.minTlsVersion DestinationRule.spec.trafficPolicy.tls Per-destination override
l7.requireRoles AuthorizationPolicy (action: ALLOW, principals) Requires waypoint
l7.denyPaths AuthorizationPolicy (action: DENY, paths) Requires waypoint
l7.rateLimits EnvoyFilter patching the local rate-limit filter Requires waypoint
audit.logAllDecisions Telemetry accessLogging OTel exporter receives logs

Each CRD is labeled velikey.com/binding=<binding-id> so the drift detector + cleanup paths can find them.

Linkerd, Consul, and Envoy

Linkerd and Consul are GA (default on) — each is CRD-validated against the live mesh and behaviorally dogfooded (real policy applied + allow/deny enforcement verified). Envoy remains behind a feature flag until its mesh data plane matures enough to dogfood rate-limit enforcement:

# Envoy Gateway 1.2+ (Gateway API + GAMMA) — still opt-in
MESH_ENVOY_ADAPTER_ENABLED=true

Translation tables:

Linkerd

Aegis clause Linkerd resource
algorithms.minTlsVersion mesh proxies always use TLS 1.3; clause honored implicitly
l7.requireRoles ServerAuthorization v1beta1 (client.meshTLS.identities — dotted-DNS Linkerd identities, not SPIFFE URIs)
l7.denyPaths rejected — Linkerd authz is allow-list only (no native path deny-list)
l7.rateLimits rejected — Linkerd has no native rate-limit support
audit.logAllDecisions Linkerd tap + access-log + OTel exporter

Consul

Aegis clause Consul resource
algorithms.minTlsVersion mesh-global Mesh config entry (spec.tls.incoming.tlsMinVersion); not per-service (Consul mTLS is on by default)
l7.requireRoles ServiceIntentions.sources[].name (allow)
l7.denyPaths ServiceIntentions.sources[].permissions[] (deny + http.pathPrefix)
l7.rateLimits rejected — Consul request rate limiting is a Consul Enterprise feature (ServiceDefaults.rateLimits)
audit.logAllDecisions Consul access logs + OTel exporter

Envoy

Targets Envoy Gateway (see ADR-0002). Envoy Gateway is gateway-centric — its policies attach to a Gateway or a Route, not a Service — so the adapter materializes a GAMMA HTTPRoute for the bound Service (parentRef kind: Service) as the attachment anchor, then attaches a BackendTrafficPolicy to that route. This design is live-validated against Envoy Gateway v1.2.4.

Aegis clause Envoy Gateway resource
(mesh routing) HTTPRoute (GAMMA, parentRef: Service) — the anchor
l7.rateLimits BackendTrafficPolicy.rateLimit (Local limiter) attached to the HTTPRoute
l7.requireRoles rejected — Envoy Gateway authz needs a configured JWT provider not in the policy model
l7.denyPaths rejected — Envoy Gateway has no path-based authorization primitive
algorithms.minTlsVersion not per-binding — mesh mTLS is global (no Gateway to attach a ClientTrafficPolicy)
audit.logAllDecisions Envoy access logs + OTel exporter

Adapter status matrix

Adapter Status Enable Installs Min version
Istio GA (default on) istioctl install (ambient) Istio 1.22+
Linkerd GA (default on) linkerd install Linkerd 2.14+
Consul GA (default on) helm install consul Consul 1.16+
Envoy Implemented, flag-gated (CRDs live-validated; rate-limiting + mesh-routing only) MESH_ENVOY_ADAPTER_ENABLED=true helm install envoy-gateway Envoy Gateway 1.2+

Envoy adapter scope

The Envoy adapter supports mesh routing + rate limiting. Authz (requireRoles/denyPaths) and a per-binding TLS floor are not supported and are rejected at apply time, because Envoy Gateway's mesh (GAMMA) model can't express them (authz needs a JWT provider; no path authz; mesh mTLS is global). Use the Istio adapter if you need those.

A cluster can be registered with any adapter regardless of its flag; the flag controls whether the reconciler applies bindings. The cluster-registration API reports bindingsEnabled so you know up front. Each non-GA adapter graduates to default-on only after its live E2E passes and it clears the production-readiness checklist — the status column is the source of truth, kept deliberately honest.

Drift detection

Every five minutes a CloudWatch EventBridge rule POSTs to /api/cron/mesh-drift-detector (HMAC-authed). The detector:

  1. Walks every registered MeshCluster.
  2. Calls the adapter's detectDrift() — which queries every Aegis-owned CRD (label velikey.com/binding=*) in the cluster.
  3. Compares each live CRD's canonical hash against the MeshBinding.appliedHash we recorded at apply time.
  4. Inserts a MeshDriftEvent row for each mismatch.
  5. Flags the binding's status as DRIFTED so the dashboard shows it.
  6. Updates MeshCluster.lastReconciledAt and lastDriftCount.

Drift surfaces in three places:

  • The dashboard's /dashboard/mesh/[clusterId] page (table of recent events + a tile counting unresolved drift).
  • A MeshDriftCount CloudWatch metric.
  • A SEV3 PagerDuty when sustained drift exceeds the threshold.

Reference

  • Gateway Mode — north-south policy enforcement; the mesh adapter extends the same policies east-west.
  • Architecture — where the mesh adapter sits relative to the control plane and agents.
  • Engineering design records (the mesh-adapter ADRs and design doc) and the runnable Istio ambient demo (scripts/demo/mesh-istio-ambient.sh) live in the source repositories for contributors.