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
kubectlandistioctlCLI 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)
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
- calicoctl
kubectl patch felixconfiguration default --type merge -p '{"spec":{"policySyncPathPrefix":"/var/run/nodeagent"}}'
calicoctl patch FelixConfiguration default --patch \
'{"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
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-injectorConfigMap 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
This manifest creates:
- ServiceEntry: Defines
dikastes.calico.cluster.localfor 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 workloadsdikastes-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.
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)
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)"
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)"
Drawbacks of ConfigMap patching:
- Not declarative (imperative command)
- Not version-controlled
- Must be re-applied after Istio upgrades
- Difficult to audit and review