This guide shows how to set up Calico Application Layer Policy (ALP) with Istio 1.28 using the modern IstioOperator approach.
- Kubernetes cluster (1.29+)
- Calico 3.31.1 installed
- Istio 1.28.1 installed
kubectlandistioctlCLI tools
The Policy Sync API allows Dikastes to communicate with Felix for policy decisions.
kubectl patch felixconfiguration default --type merge -p '{"spec":{"policySyncPathPrefix":"/var/run/nodeagent"}}'Verify:
kubectl get felixconfiguration default -o yaml | grep policySyncPathPrefixCreate 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
values:
sidecarInjectorWebhook:
templates:
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
# This allows Envoy to communicate with Dikastes via Unix socket
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
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
# IMPORTANT: Patch istio-proxy to mount dikastes socket
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.ioUpdate your Istio installation with the Dikastes templates:
istioctl install -f istio-operator-dikastes.yaml -yThis will update the istio-sidecar-injector ConfigMap with the custom templates.
Check that the templates were loaded:
kubectl get configmap -n istio-system istio-sidecar-injector -o yaml | grep "dikastes:" -A 5You should see both dikastes and dikastes-gateway templates.
Deploy the EnvoyFilter and ServiceEntry for Dikastes integration:
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.31.2/manifests/alp/istio-app-layer-policy-envoy-v3.yamlThis configures:
- ServiceEntry:
dikastes.calico.cluster.localfor Unix socket communication - DestinationRule: Disables mTLS for local socket
- EnvoyFilter: Configures Envoy's ext_authz filter to call Dikastes
kubectl create namespace bookinfo
kubectl label namespace bookinfo istio-injection=enabledCreate test-app.yaml:
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: httpbin
namespace: bookinfo
---
apiVersion: v1
kind: Service
metadata:
name: httpbin
namespace: bookinfo
labels:
app: httpbin
spec:
ports:
- name: http
port: 8000
targetPort: 80
selector:
app: httpbin
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin
namespace: bookinfo
spec:
replicas: 1
selector:
matchLabels:
app: httpbin
template:
metadata:
labels:
app: httpbin
annotations:
# KEY: Inject both Istio sidecar AND Dikastes sidecar
inject.istio.io/templates: sidecar,dikastes
spec:
serviceAccountName: httpbin
containers:
- name: httpbin
image: kennethreitz/httpbin
ports:
- containerPort: 80
---
# Client pod for testing
apiVersion: v1
kind: ServiceAccount
metadata:
name: client
namespace: bookinfo
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: client
namespace: bookinfo
spec:
replicas: 1
selector:
matchLabels:
app: client
template:
metadata:
labels:
app: client
annotations:
inject.istio.io/templates: sidecar
spec:
serviceAccountName: client
containers:
- name: client
image: curlimages/curl:latest
command: ["/bin/sh"]
args: ["-c", "while true; do sleep 30; done"]Deploy:
kubectl apply -f test-app.yamlWait for pods to be ready:
kubectl wait --for=condition=ready pod -l app=httpbin -n bookinfo --timeout=180sCheck that Dikastes container was injected:
kubectl get pod -l app=httpbin -n bookinfo -o jsonpath='{.items[0].spec.containers[*].name}'Expected output: dikastes httpbin
Check pod status (should show 3/3 containers):
kubectl get pods -n bookinfoThe three containers are:
dikastes- Calico L7 policy enforcerhttpbin- Applicationistio-proxy- Envoy sidecar (as native Kubernetes sidecar)
Check Dikastes logs:
kubectl logs -n bookinfo -l app=httpbin -c dikastesYou should see:
Successfully connected to Policy Sync server
Starting synchronization with Policy Sync server
Test that Dikastes is enforcing authorization:
kubectl exec -n bookinfo deploy/client -- curl -s -o /dev/null -w "%{http_code}\n" http://httpbin:8000/getExpected: 403 (Forbidden)
This confirms Dikastes is actively enforcing L7 authorization with default-deny behavior.
- Istio Proxy (Envoy) intercepts all HTTP traffic
- ext_authz filter forwards authorization checks to Dikastes via Unix socket
- Dikastes queries Felix for policy decisions via CSI-mounted socket
- Felix returns allow/deny based on Calico policies
- Envoy allows or blocks the request
Envoy → /var/run/dikastes/dikastes.sock → Dikastes
Dikastes → /var/run/felix/nodeagent/socket → Felix (via CSI driver)
Istio 1.28 uses Kubernetes 1.29+ native sidecar support where istio-proxy is an init container with restartPolicy: Always. This provides:
- Guaranteed startup ordering
- Automatic restart on failure
- Proper lifecycle management
To inject Dikastes into your workloads, add this annotation:
metadata:
annotations:
inject.istio.io/templates: sidecar,dikastesFor gateway workloads, use:
metadata:
annotations:
inject.istio.io/templates: sidecar,dikastes-gatewayCheck that namespace has Istio injection enabled:
kubectl get namespace bookinfo -o yaml | grep istio-injectionVerify templates in ConfigMap:
kubectl get configmap -n istio-system istio-sidecar-injector -o yaml | grep dikastesCheck pod events:
kubectl describe pod -n bookinfo -l app=httpbinCheck Dikastes logs:
kubectl logs -n bookinfo -l app=httpbin -c dikastesThis is expected behavior! Dikastes defaults to deny-all when no ApplicationLayerPolicy is configured. This is a secure-by-default posture.
To allow traffic, you need to create Calico policies (specific policy configuration depends on your Calico distribution).
Compared to the old ConfigMap patching approach:
- ✅ Declarative: Full YAML manifest in version control
- ✅ Upgrade-safe: Survives Istio upgrades
- ✅ GitOps-friendly: Use
istioctl manifest generateto generate manifests - ✅ Maintainable: Easy to review and modify
- ✅ Documented: Clear intent in IstioOperator spec
- Create ApplicationLayerPolicy resources to define allow rules
- Test HTTP method filtering (GET, POST, etc.)
- Test path-based authorization
- Implement identity-based access control
Version: Tested with Calico 3.31.2 and Istio 1.28.1 Date: 2025-12-06