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:
What you get:
- A k3d cluster with Gateway API CRDs installed.
- Istio ambient mode installed (revision
aegis). - A sample
httpbinservice labeled into ambient. PeerAuthentication(STRICT mTLS) +DestinationRule(ISTIO_MUTUAL) applied with the same shape the IstioAdapter renderers emit.- Assertions: STRICT mTLS landed; ztunnel pods are healthy.
The demo tears down the k3d cluster on exit.
Production install
This calls POST /api/mesh/adapters/istio/install, which:
- Validates the operator's kubeconfig is reachable.
- Runs
istioctl install --set profile=ambient -yagainst the cluster (with revisionaegis). - Records the cluster as
MeshClusterin the Axis DB.
Bind a policy to a service
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:
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:
- Walks every registered
MeshCluster. - Calls the adapter's
detectDrift()— which queries every Aegis-owned CRD (labelvelikey.com/binding=*) in the cluster. - Compares each live CRD's canonical hash against the
MeshBinding.appliedHashwe recorded at apply time. - Inserts a
MeshDriftEventrow for each mismatch. - Flags the binding's status as
DRIFTEDso the dashboard shows it. - Updates
MeshCluster.lastReconciledAtandlastDriftCount.
Drift surfaces in three places:
- The dashboard's
/dashboard/mesh/[clusterId]page (table of recent events + a tile counting unresolved drift). - A
MeshDriftCountCloudWatch 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.