Skip to main content
Calico Open Source 3.31 (latest) documentation

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 (recommended)
  • Istio v1.22+ (minimum required for Kubernetes native sidecar support)
note

This guide requires Istio 1.22+ with Kubernetes native sidecars. Traditional sidecar injection is not supported. For legacy Istio versions (v1.15.2, v1.10.2), see Legacy Installation.

Required Kubernetes versions

  • Kubernetes v1.29+: Required for native sidecar support

How to

This guide covers the usage of IstioOperator

For the ConfigMap patching method, see Legacy Installation.

1. Enable application layer policy

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

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

Optional: If using the Calico operator, disable deprecated Flex Volumes:

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

2. 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 -o install-istio.sh
# Review the script before running it, then install:
ISTIO_VERSION=1.28.1 sh install-istio.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.

3. Configure Istio with Dikastes injection templates

Option A: use IstioOperator directly

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
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
initContainers:
- name: istio-proxy
volumeMounts:
- mountPath: /var/run/dikastes
name: dikastes-sock
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
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
initContainers:
- name: istio-proxy
volumeMounts:
- mountPath: /var/run/dikastes
name: dikastes-sock
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.

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

4. 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.3/manifests/alp/istio-app-layer-policy-envoy-v3.yaml

View sample manifest

This manifest creates:

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

5. Enable Istio injection for namespaces

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

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

6. 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

7. 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

8. (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

Create Calico network policies

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

note

The specific policy Custom Resource Definitions 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 jsonpath='{.metadata.labels.istio-injection}'

Should show: 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 jsonpath='{.data.values}' | grep -o "dikastes:" | wc -l

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 (automatically installed with Calico):

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

All CSI driver pods should be Running. The CSI driver is required for Dikastes to access the Felix Policy Sync API.

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, disable BPF connect-time load balancing:

kubectl patch felixconfiguration default --type merge -p '{"spec":{"bpfConnectTimeLoadBalancing":"Disabled"}}'

Legacy Installation (ConfigMap Patching)

note

This method is supported but not recommended for new deployments. Use the IstioOperator method 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.3/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)"

View sample manifest

For Istio v1.10.x:

curl https://raw.githubusercontent.com/projectcalico/calico/v3.31.3/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)"

View sample manifest

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