Tier 2: Authenticate clients with provider-specific TLS validation¶
When to use this approach¶
Use Tier 2 X.509 authentication when you need defense-in-depth security but don't have Gateway API v1.5+ yet:
- Defense-in-depth security: Same two-layer validation as Tier 1 (TLS + Application)
- Older Gateway API versions: Works with Gateway API versions prior to v1.5
- Provider flexibility: Supports Istio (via EnvoyFilter) and Envoy Gateway (via EnvoyPatchPolicy)
- Migration path: Clear upgrade path to Tier 1 when you adopt Gateway API v1.5+
This approach provides the same security guarantees as Tier 1 but requires provider-specific configuration knowledge.
How it works¶
Tier 2 implements the same two-layer validation model as Tier 1, but uses provider-specific resources for TLS configuration:
Layer 1 (TLS/L4): Gateway validates client certificates during TLS handshake
- Configured via EnvoyFilter (Istio) or EnvoyPatchPolicy (Envoy Gateway)
- Client presents certificate during mTLS handshake
- Gateway validates against configured CA certificates
- Invalid, expired, or untrusted certificates are rejected
- Gateway sets
x-forwarded-client-cert(XFCC) header with certificate details - Incoming XFCC headers from clients are sanitized
Layer 2 (Application/L7): Authorino validates certificates from XFCC header
- Identical to Tier 1: AuthPolicy extracts certificate from XFCC header
- Applies fine-grained validation using label selectors on CA Secrets
- Enables multi-CA trust scenarios
Result: Request proceeds only if both layers succeed.
Before you begin¶
Ensure you have:
- Kubernetes cluster: Any supported version
- Gateway API: Any version (does not require v1.5)
- Gateway implementation: Istio (any version) or Envoy Gateway
- Kuadrant Operator: Installed with Kuadrant instance deployed
- Provider knowledge: Familiarity with EnvoyFilter (Istio) or EnvoyPatchPolicy (Envoy Gateway)
Choose your provider¶
Select the guide for your gateway provider:
- Istio: Configure using EnvoyFilter
- Envoy Gateway: Configure using EnvoyPatchPolicy
Both approaches achieve the same security outcome and use identical AuthPolicy configuration.
Istio: EnvoyFilter configuration¶
Step 1: Prepare CA and client certificates¶
Generate or obtain CA and client certificates. Client certificates must include extendedKeyUsage=clientAuth.
# Generate CA
openssl req -x509 -sha512 -nodes \
-days 365 \
-newkey rsa:4096 \
-subj "/CN=Test CA/O=Kuadrant/C=US" \
-addext basicConstraints=CA:TRUE \
-addext keyUsage=digitalSignature,keyCertSign \
-keyout /tmp/ca.key \
-out /tmp/ca.crt
# Create X.509 v3 extensions for client certificate
cat > /tmp/x509v3.ext << EOF
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage=digitalSignature,nonRepudiation,keyEncipherment,dataEncipherment
extendedKeyUsage=clientAuth
EOF
# Generate client certificate
openssl genrsa -out /tmp/client.key 4096
openssl req -new \
-subj "/CN=test-client/O=Kuadrant/C=US" \
-key /tmp/client.key \
-out /tmp/client.csr
openssl x509 -req -sha512 \
-days 365 \
-CA /tmp/ca.crt \
-CAkey /tmp/ca.key \
-CAcreateserial \
-extfile /tmp/x509v3.ext \
-in /tmp/client.csr \
-out /tmp/client.crt
Important
Client certificates must include extendedKeyUsage=clientAuth for Authorino validation to succeed.
Step 2: Create CA certificate resources¶
# ConfigMap for Gateway TLS validation (Layer 1)
kubectl create configmap client-ca-bundle \
-n gateway-system \
--from-file=ca.crt=/tmp/ca.crt
# Secret for Authorino validation (Layer 2)
kubectl create secret tls trusted-client-ca \
-n kuadrant-system \
--cert=/tmp/ca.crt \
--key=/tmp/ca.key
# Label the secret so Authorino can discover it
kubectl label secret trusted-client-ca \
-n kuadrant-system \
authorino.kuadrant.io/managed-by=authorino \
app.kubernetes.io/name=trusted-client
Step 3: Configure Gateway¶
Create a Gateway without frontend TLS validation (handled by EnvoyFilter instead):
kubectl apply -f https://raw.githubusercontent.com/Kuadrant/kuadrant-operator/refs/heads/main/examples/x509-authentication/gateway-tier2.yaml
This creates:
- cert-manager Issuer: Self-signed certificate issuer for the gateway server certificate
- TLSPolicy: Kuadrant policy to manage the gateway's server TLS certificate
- Gateway: Standard Gateway without
spec.tls.frontend.default.validation(client certificate validation handled by EnvoyFilter)
Step 4: Mount CA certificate into gateway pods¶
Patch the gateway deployment to mount the CA certificate Secret:
Warning
This approach directly patches the gateway deployment, which is managed by the gateway controller. In production environments, use proper configuration mechanisms (e.g., Istio Helm values or IstioOperator overlays) to ensure changes persist across controller reconciliations.
kubectl patch deployment -n gateway-system \
$(kubectl get deployment -n gateway-system -l gateway.networking.k8s.io/gateway-name=mtls-gateway -o jsonpath='{.items[0].metadata.name}') \
--type=json -p='[
{
"op": "add",
"path": "/spec/template/spec/volumes/-",
"value": {
"name": "client-ca-bundle",
"configMap": {
"name": "client-ca-bundle"
}
}
},
{
"op": "add",
"path": "/spec/template/spec/containers/0/volumeMounts/-",
"value": {
"name": "client-ca-bundle",
"mountPath": "/etc/certs",
"readOnly": true
}
}
]'
Step 5: Create EnvoyFilter for mTLS validation¶
Create an EnvoyFilter to configure Envoy's DownstreamTlsContext for client certificate validation:
kubectl apply -f -<<EOF
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: mtls-validation
namespace: gateway-system
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: mtls-gateway
configPatches:
- applyTo: FILTER_CHAIN
match:
context: GATEWAY
listener:
portNumber: 443
patch:
operation: MERGE
value:
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
commonTlsContext:
validationContext:
trustedCa:
filename: /etc/certs/ca.crt
requireClientCertificate: true
EOF
Step 6: Deploy application and create HTTPRoute¶
# Deploy httpbin application
kubectl apply -f https://raw.githubusercontent.com/Kuadrant/kuadrant-operator/refs/heads/main/examples/x509-authentication/httpbin.yaml
# Create HTTPRoute
kubectl apply -f https://raw.githubusercontent.com/Kuadrant/kuadrant-operator/refs/heads/main/examples/x509-authentication/httproute.yaml
Step 7: Configure AuthPolicy¶
The AuthPolicy configuration is identical to Tier 1 - it extracts the certificate from the XFCC header and validates it:
kubectl apply -f https://raw.githubusercontent.com/Kuadrant/kuadrant-operator/refs/heads/main/examples/x509-authentication/authpolicy.yaml
This creates an AuthPolicy that:
- Extracts the certificate from the
x-forwarded-client-certheader - Validates the certificate chain against CA certificates labeled
app.kubernetes.io/name: trusted-client - Enforces authorization based on certificate Organization attribute
- Injects certificate attributes into request headers
Envoy Gateway: EnvoyPatchPolicy configuration¶
Step 1: Prepare CA and client certificates¶
Same as Istio (see Istio Step 1)
Step 2: Create CA certificate resources¶
Same as Istio (see Istio Step 2)
Step 3: Configure Gateway¶
Similar to Istio (see Istio Step 3), but adjust gatewayClassName for Envoy Gateway.
Step 4: Mount CA certificate into gateway pods¶
Patch the gateway deployment to mount the CA certificate Secret:
Warning
This approach directly patches the gateway deployment, which is managed by the Envoy Gateway controller. In production environments, use proper configuration mechanisms (e.g., EnvoyProxy resource with extraVolumes) to ensure changes persist across controller reconciliations.
kubectl patch deployment -n gateway-system \
$(kubectl get deployment -n gateway-system -l gateway.envoyproxy.io/owning-gateway-name=mtls-gateway -o jsonpath='{.items[0].metadata.name}') \
--type=json -p='[
{
"op": "add",
"path": "/spec/template/spec/volumes/-",
"value": {
"name": "client-ca-bundle",
"configMap": {
"name": "client-ca-bundle"
}
}
},
{
"op": "add",
"path": "/spec/template/spec/containers/0/volumeMounts/-",
"value": {
"name": "client-ca-bundle",
"mountPath": "/etc/certs",
"readOnly": true
}
}
]'
Step 5: Create EnvoyPatchPolicy for mTLS validation¶
Create an EnvoyPatchPolicy to configure client certificate validation:
kubectl apply -f -<<EOF
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyPatchPolicy
metadata:
name: mtls-validation
namespace: gateway-system
spec:
targetRef:
group: gateway.networking.k8s.io
kind: Gateway
name: mtls-gateway
type: JSONPatch
jsonPatches:
- type: "type.googleapis.com/envoy.config.listener.v3.Listener"
name: https
operation:
op: add
path: "/filter_chains/0/transport_socket/typed_config/commonTlsContext/validationContext"
value:
trustedCa:
filename: /etc/certs/ca.crt
- type: "type.googleapis.com/envoy.config.listener.v3.Listener"
name: https
operation:
op: add
path: "/filter_chains/0/transport_socket/typed_config/requireClientCertificate"
value: true
EOF
Step 6: Deploy application and create HTTPRoute¶
Same as Istio (see Istio Step 6)
Step 7: Configure AuthPolicy¶
Same as Istio (see Istio Step 7)
Verify defense-in-depth security¶
Testing is identical to Tier 1. Run the same test scenarios:
GATEWAY_IP=$(kubectl get gateway mtls-gateway -n gateway-system -o jsonpath='{.status.addresses[0].value}')
# Test 1: Valid certificate → HTTP 200
curl -ik https://httpbin.$GATEWAY_IP.nip.io/get \
--cert /tmp/client.crt \
--key /tmp/client.key
# Test 2: No certificate → TLS handshake fails
curl -ik https://httpbin.$GATEWAY_IP.nip.io/get
# Test 3: Untrusted certificate → TLS handshake fails
curl -ik https://httpbin.$GATEWAY_IP.nip.io/get \
--cert /tmp/untrusted.crt \
--key /tmp/untrusted.key
# Test 4: Valid cert, wrong attributes → HTTP 403
curl -ik https://httpbin.$GATEWAY_IP.nip.io/get \
--cert /tmp/unauthorized-client.crt \
--key /tmp/unauthorized-client.key
Migration to Tier 1¶
When you upgrade to Gateway API v1.5+ and your gateway provider supports spec.tls.frontend.default.validation, migrate from Tier 2 to Tier 1:
Migration steps¶
-
Update Gateway resource to include frontend TLS validation:
-
Remove provider-specific resource:
-
Keep AuthPolicy unchanged: AuthPolicy configuration remains identical
-
Verify: Run the same test scenarios to confirm migration succeeded
Why migrate?¶
- Standardization: Uses Gateway API standard instead of vendor-specific resources
- Maintainability: Simpler configuration, easier to understand
- Portability: Gateway API configuration is portable across providers
- Future-proof: Gateway API v1.5 is the recommended approach going forward
Troubleshooting¶
EnvoyFilter not applied¶
Symptoms: TLS handshake succeeds without client certificate
Resolution:
# Verify EnvoyFilter exists
kubectl get envoyfilter mtls-validation -n gateway-system
# Check targetRef points to gateway
kubectl get envoyfilter mtls-validation -n gateway-system -o yaml | grep -A 3 targetRefs
# Check Envoy configuration was patched
kubectl exec -n gateway-system \
$(kubectl get pod -n gateway-system -l gateway.networking.k8s.io/gateway-name=mtls-gateway -o jsonpath='{.items[0].metadata.name}') \
-- curl -s localhost:15000/config_dump?include_eds | grep -A 20 validation_context
CA certificate not mounted¶
Symptoms: Gateway logs show "failed to load trusted CA"
Resolution:
# Verify ConfigMap exists
kubectl get configmap client-ca-bundle -n gateway-system
# Check volume mount in gateway pod
kubectl describe pod -n gateway-system -l gateway.networking.k8s.io/gateway-name=mtls-gateway | grep -A 5 Mounts
# Verify file exists in pod
kubectl exec -n gateway-system \
$(kubectl get pod -n gateway-system -l gateway.networking.k8s.io/gateway-name=mtls-gateway -o jsonpath='{.items[0].metadata.name}') \
-- cat /etc/certs/ca.crt
XFCC header not set correctly¶
Symptoms: Authorino rejects with "certificate not found"
Resolution:
# Verify forward_client_cert_details configuration
kubectl exec -n gateway-system \
$(kubectl get pod -n gateway-system -l gateway.networking.k8s.io/gateway-name=mtls-gateway -o jsonpath='{.items[0].metadata.name}') \
-- curl -s localhost:15000/config_dump?include_eds | grep -i forward_client_cert
# Check Envoy access logs for XFCC
kubectl logs -n gateway-system -l gateway.networking.k8s.io/gateway-name=mtls-gateway | grep XFCC
See also¶
- X.509 Authentication User Guides - Choose the right tier
- Tier 1 Guide - Recommended approach with Gateway API v1.5+
- X.509 Authentication Overview - Architecture and security
- Istio EnvoyFilter Documentation
- Envoy Gateway EnvoyPatchPolicy
- Envoy DownstreamTlsContext