Skip to content

Instantly share code, notes, and snippets.

@electricjesus
Created December 6, 2025 09:31
Show Gist options
  • Select an option

  • Save electricjesus/56515a2d526ba2ee84e0cf5a2f74cf31 to your computer and use it in GitHub Desktop.

Select an option

Save electricjesus/56515a2d526ba2ee84e0cf5a2f74cf31 to your computer and use it in GitHub Desktop.
Updated Calico + Istio App Layer Policy Documentation (with Istio 1.28 + IstioOperator)

Enforce Calico network policy for Istio service mesh

Overview

You can enforce Calico network policy for Istio application layer policy using the Dikastes sidecar. Dikastes enables Calico to integrate with Istio's Envoy proxy to enforce fine-grained Layer 7 (HTTP) policies.

Value

  • Pod traffic controls for Istio-enabled apps: Lets you restrict ingress traffic inside and outside pods and mitigate common threats to Istio-enabled apps.
  • Security alignment with zero trust: Supports zero-trust network models through traffic encryption, multiple enforcement points, and multiple identity criteria for authentication.
  • Familiar policy language: Apply Kubernetes network policies and Calico network policies that you already know.

Before you begin

Required

  • Calico CNI is installed and configured
  • kubectl and istioctl CLI tools are installed
  • MutatingAdmissionWebhook admission controller is enabled

Supported Istio versions

  • Istio v1.28.1 (tested and verified - recommended)
  • Istio v1.18+ (should work with the IstioOperator approach)
  • Legacy: Istio v1.15.2, v1.10.2 (use ConfigMap patching method - see Legacy Installation)

Note: Istio v1.9.x and lower are not supported.

Recommended Kubernetes versions

  • Kubernetes v1.29+: Required for native sidecar support used by Istio 1.22+

How to

This guide covers the modern IstioOperator approach which is declarative, version-controllable, and upgrade-safe.

For the legacy ConfigMap patching approach, see Legacy Installation.

1. Enable application layer policy

Enable the Policy Sync API in Felix to allow Dikastes to query policy decisions.

Using kubectl:

kubectl patch felixconfiguration default --type merge -p '{"spec":{"policySyncPathPrefix":"/var/run/nodeagent"}}'

Using calicoctl:

calicoctl patch FelixConfiguration default --patch \
  '{"spec": {"policySyncPathPrefix": "/var/run/nodeagent"}}'

Optional: If using the Calico operator, disable deprecated flexvolumes:

kubectl patch installation default --type=merge -p '{"spec": {"flexVolumePath": "None"}}'

2. Install Calico CSI driver

The CSI driver mounts the Felix Policy Sync socket into Dikastes containers.

kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.31.2/manifests/csi-driver.yaml

Verify the CSI driver is running:

kubectl get pods -n calico-system -l k8s-app=csi-node-driver

3. Install Istio

Follow the upstream Istio installation documentation to install Istio if not already installed.

For testing, you can use:

curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.28.1 sh -
cd istio-1.28.1
export PATH=$PWD/bin:$PATH

Note: Do not run istioctl install yet if you want to configure Dikastes templates during installation. See the next step.

4. Configure Istio with Dikastes injection templates

Option A: IstioOperator Approach (Recommended)

Create a file named istio-operator-dikastes.yaml:

apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  name: istio-with-dikastes
  namespace: istio-system
spec:
  profile: minimal  # or 'default' if you need gateways

  values:
    sidecarInjectorWebhook:
      templates:
        # Template for workload pods
        dikastes: |
          spec:
            containers:
            - name: dikastes
              image: quay.io/calico/dikastes:v3.31.2
              args:
              - server
              - -l
              - /var/run/dikastes/dikastes.sock
              - -d
              - /var/run/felix/nodeagent/socket
              securityContext:
                allowPrivilegeEscalation: false
                runAsGroup: 999
                runAsNonRoot: true
                runAsUser: 999
              livenessProbe:
                exec:
                  command:
                  - /healthz
                  - liveness
                initialDelaySeconds: 3
                periodSeconds: 3
              readinessProbe:
                exec:
                  command:
                  - /healthz
                  - readiness
                initialDelaySeconds: 3
                periodSeconds: 3
              volumeMounts:
              - mountPath: /var/run/dikastes
                name: dikastes-sock
              - mountPath: /var/run/felix
                name: felix-sync
            volumes:
            - name: dikastes-sock
              emptyDir:
                medium: Memory
            - name: felix-sync
              csi:
                driver: csi.tigera.io

        # Template for gateway pods (runs as root)
        dikastes-gateway: |
          spec:
            containers:
            - name: dikastes
              image: quay.io/calico/dikastes:v3.31.2
              args:
              - server
              - -l
              - /var/run/dikastes/dikastes.sock
              - -d
              - /var/run/felix/nodeagent/socket
              securityContext:
                allowPrivilegeEscalation: false
                runAsGroup: 0
                runAsNonRoot: false
                runAsUser: 0
              livenessProbe:
                exec:
                  command:
                  - /healthz
                  - liveness
                initialDelaySeconds: 3
                periodSeconds: 3
              readinessProbe:
                exec:
                  command:
                  - /healthz
                  - readiness
                initialDelaySeconds: 3
                periodSeconds: 3
              volumeMounts:
              - mountPath: /var/run/dikastes
                name: dikastes-sock
              - mountPath: /var/run/felix
                name: felix-sync
            volumes:
            - name: dikastes-sock
              emptyDir:
                medium: Memory
            - name: felix-sync
              csi:
                driver: csi.tigera.io

Install or update Istio with the Dikastes templates:

istioctl install -f istio-operator-dikastes.yaml -y

This will:

  • Install Istio (if not already installed)
  • Update the istio-sidecar-injector ConfigMap with the Dikastes templates
  • Make the templates available for pod annotation-based injection

Verify the templates were loaded:

kubectl get configmap -n istio-system istio-sidecar-injector -o yaml | grep "dikastes:" -A 5

You should see both dikastes and dikastes-gateway templates.

Benefits of the IstioOperator approach:

  • Declarative: Full configuration in a version-controlled YAML file
  • Upgrade-safe: Templates persist across Istio upgrades
  • GitOps-friendly: Use istioctl manifest generate to generate Kubernetes manifests
  • Maintainable: Easy to review, modify, and understand

Option B: Generate Manifests for GitOps

If you prefer to generate Kubernetes manifests instead of applying directly:

istioctl manifest generate -f istio-operator-dikastes.yaml > istio-with-dikastes.yaml
kubectl apply -f istio-with-dikastes.yaml

5. Add Envoy authorization services

Configure Istio's Envoy proxies to use Dikastes as an external authorization service.

kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.31.2/manifests/alp/istio-app-layer-policy-envoy-v3.yaml

This manifest creates:

  • ServiceEntry: Defines dikastes.calico.cluster.local for Unix socket communication
  • DestinationRule: Disables mTLS for local socket communication
  • EnvoyFilter: Configures Envoy's ext_authz filter to call Dikastes

6. Enable Istio injection for namespaces

Label the namespace where you want to deploy Istio-enabled workloads:

kubectl label namespace <your-namespace> istio-injection=enabled

7. Annotate pods to inject Dikastes

To inject the Dikastes sidecar into your pods, add the following annotation to your pod template:

For regular workloads:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  template:
    metadata:
      annotations:
        inject.istio.io/templates: sidecar,dikastes
    spec:
      # ... your pod spec

For gateway workloads:

metadata:
  annotations:
    inject.istio.io/templates: sidecar,dikastes-gateway

The key difference:

  • dikastes: Runs as non-root user (999) - for application workloads
  • dikastes-gateway: Runs as root (0) - for ingress/egress gateways

8. Verify Dikastes injection

After deploying a workload with the annotation:

# Check that Dikastes container is present
kubectl get pod -l app=<your-app> -n <your-namespace> -o jsonpath='{.items[0].spec.containers[*].name}'

You should see: dikastes <your-app-container> ...

Check pod status (should show all containers ready):

kubectl get pods -n <your-namespace>

Check Dikastes logs:

kubectl logs -n <your-namespace> -l app=<your-app> -c dikastes

You should see:

Successfully connected to Policy Sync server
Starting synchronization with Policy Sync server

9. (Optional) Enable strict mTLS

For enhanced security, enable strict mutual TLS between services:

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default-strict-mode
  namespace: istio-system
spec:
  mtls:
    mode: STRICT

Apply:

kubectl apply -f <filename>.yaml

Architecture

How it works

When a request flows through an Istio-enabled pod with Dikastes:

  1. Istio Proxy (Envoy) intercepts the HTTP request
  2. ext_authz filter sends an authorization check to Dikastes via Unix socket (/var/run/dikastes/dikastes.sock)
  3. Dikastes queries Felix for policy decisions via the CSI-mounted socket (/var/run/felix/nodeagent/socket)
  4. Felix evaluates Calico network policies and returns allow/deny
  5. Dikastes responds to Envoy with the authorization decision
  6. Envoy allows or blocks the request based on the decision

Communication paths

HTTP Request → Envoy → ext_authz → Dikastes → Felix Policy Sync API → Policy Decision

Kubernetes native sidecars

Istio 1.22+ (including 1.28) uses Kubernetes 1.29+ native sidecar support. The istio-proxy runs as an init container with restartPolicy: Always, which provides:

  • ✅ Guaranteed startup ordering (sidecar starts before application)
  • ✅ Automatic restart on failure
  • ✅ Proper lifecycle management

This is why you'll see the istio-proxy in init containers but the pod shows 3/3 containers running.

Create Calico network policies

With Dikastes deployed, you can now create Calico network policies to enforce Layer 7 access control.

Note: The specific policy CRDs available depend on your Calico distribution (OSS vs Enterprise). Consult your Calico documentation for policy syntax.

Example conceptual policy (syntax may vary):

apiVersion: projectcalico.org/v3
kind: NetworkPolicy
metadata:
  name: allow-http-get
  namespace: default
spec:
  selector: app == "myapp"
  types:
  - Ingress
  ingress:
  - action: Allow
    protocol: TCP
    destination:
      ports:
      - 80

Default behavior

Without explicit allow policies, Dikastes enforces a default-deny posture for security. All HTTP requests will receive a 403 Forbidden response until you create policies that explicitly allow traffic.

Troubleshooting

Dikastes container not injected

Check namespace label:

kubectl get namespace <your-namespace> -o yaml | grep istio-injection

Should show: istio-injection: enabled

Check pod annotation:

kubectl get pod <pod-name> -n <your-namespace> -o yaml | grep inject.istio.io/templates

Should show: inject.istio.io/templates: sidecar,dikastes

Verify templates in ConfigMap:

kubectl get configmap -n istio-system istio-sidecar-injector -o yaml | grep -c "dikastes:"

Should return 2 (one for each template).

Pod stuck in PodInitializing or CrashLoopBackOff

Check pod events:

kubectl describe pod <pod-name> -n <your-namespace>

Check Dikastes logs:

kubectl logs <pod-name> -n <your-namespace> -c dikastes

Check CSI driver:

kubectl get pods -n calico-system -l k8s-app=csi-node-driver

All CSI driver pods should be Running.

Verify Felix Policy Sync API:

kubectl get felixconfiguration default -o yaml | grep policySyncPathPrefix

Should show: policySyncPathPrefix: /var/run/nodeagent

All requests returning 403

This is expected behavior when no allow policies are configured. Dikastes enforces default-deny for security.

To allow traffic, create Calico network policies that explicitly permit the desired traffic.

Envoy cannot reach istio-pilot

If you have egress policies applied to your pods, Envoy needs access to the Istio control plane.

Apply the allow policy:

kubectl apply -f https://docs.tigera.io/files/allow-istio-pilot.yaml

Warning about BPF load balancing

If you see this warning during installation:

detected Calico CNI with 'bpfConnectTimeLoadBalancing=TCP';
this must be set to 'bpfConnectTimeLoadBalancing=Disabled'

This may affect connection-level load balancing but does not prevent basic functionality. For production deployments, consider adjusting the Calico configuration as recommended.

Legacy Installation (ConfigMap Patching)

Note: This method is supported but not recommended for new deployments. Use the IstioOperator approach instead.

If you need to use the legacy ConfigMap patching method for Istio 1.15 or 1.10:

For Istio v1.15.x:

curl https://raw.githubusercontent.com/projectcalico/calico/v3.31.2/manifests/alp/istio-inject-configmap-1.15.yaml -o istio-inject-configmap.yaml
kubectl patch configmap -n istio-system istio-sidecar-injector --patch "$(cat istio-inject-configmap.yaml)"

For Istio v1.10.x:

curl https://raw.githubusercontent.com/projectcalico/calico/v3.31.2/manifests/alp/istio-inject-configmap-1.10.yaml -o istio-inject-configmap.yaml
kubectl patch configmap -n istio-system istio-sidecar-injector --patch "$(cat istio-inject-configmap.yaml)"

Drawbacks of ConfigMap patching:

  • ❌ Not declarative (imperative command)
  • ❌ Not version-controlled
  • ❌ Must be re-applied after Istio upgrades
  • ❌ Difficult to audit and review

Additional resources


Last Updated: 2025-12-06 Tested Versions: Calico v3.31.2, Istio v1.28.1, Kubernetes v1.33

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment