X.509 Client Certificate Authentication¶
This guide demonstrates X.509 client certificate authentication using Gateway API v1.5+ with frontend TLS validation (RFC 0015 / Tier 1).
Overview¶
Tier 1 of RFC 0015 implements defense-in-depth security with two validation layers:
- Layer 1 (TLS/L4): Gateway validates client certificates during the TLS handshake
- Layer 2 (Application/L7): Authorino validates certificates using label selectors for multi-CA trust
Request Flow¶
- Client initiates HTTPS connection with client certificate
- Gateway validates certificate against
caCertificateRefsduring TLS handshake - Invalid/expired/untrusted certificates are rejected at the TLS layer
- For valid certificates, gateway terminates TLS and populates the
x-forwarded-client-cert(XFCC) header - Wasm-shim forwards XFCC header to Authorino in CheckRequest
- Authorino extracts and validates the certificate from the XFCC header
- Authorino applies fine-grained validation using label selectors on CA Secrets
- Request proceeds only if both layers succeed
Prerequisites¶
- Kubernetes 1.31+ (required for Gateway API v1.5 CEL validation rules in CRDs)
- Gateway API v1.5+ with
spec.tls.frontend.default.validationsupport - Istio v1.28+ or compatible Gateway implementation supporting XFCC header forwarding and frontend TLS validation (GEP-91)
- Sail Operator: Requires Sail v1.28.0+ with Istio v1.28.0+ configured in the Istio CR
- Upstream Istio: Requires Istio v1.28.0+ installed via
istioctl - Note: Istio 1.27.x and earlier do NOT support Gateway API v1.5's
spec.tls.frontendfield - Kuadrant Operator installed
- Kuadrant instance deployed
Setup¶
Step 1: Generate Test Certificates¶
For testing purposes, generate self-signed certificates:
# Generate CA private key and certificate
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 file for client certificate
cat > /tmp/x509v3.ext << EOF
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage=digitalSignature,nonRepudiation,keyEncipherment,dataEncipherment
extendedKeyUsage=clientAuth
EOF
# Generate client private key and certificate signed by the CA
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 1 \
-CA /tmp/ca.crt \
-CAkey /tmp/ca.key \
-CAcreateserial \
-extfile /tmp/x509v3.ext \
-in /tmp/client.csr \
-out /tmp/client.crt
# Update the ConfigMap and Secret with the generated CA certificate
kubectl create configmap client-ca-bundle -n gateway-system \
--from-file=ca.crt=/tmp/ca.crt
kubectl create secret tls trusted-client-ca -n kuadrant-system \
--cert=/tmp/ca.crt \
--key=/tmp/ca.key
kubectl label secret trusted-client-ca -n kuadrant-system \
authorino.kuadrant.io/managed-by=authorino \
app.kubernetes.io/name=trusted-client
Note: For production, use certificates from your PKI infrastructure and consider using cert-manager for automated certificate management.
Step 2: Deploy Resources¶
Deploy the example resources:
This creates:
- Gateway (
mtls-gateway): Gateway with mTLS configured - Deployment/Service (
httpbin): Test application - HTTPRoute (
httpbin-route): Routes traffic to the protected service - AuthPolicy (
x509-auth-policy): X.509 authentication using XFCC header
Configuration Details¶
Topology Overview¶
The following diagram illustrates the architecture and relationships between components:
%%{init: {'theme':'base', 'themeVariables': { 'primaryColor':'#fff','primaryTextColor':'#000','primaryBorderColor':'#000','lineColor':'#000','secondaryColor':'#fff','tertiaryColor':'#fff','clusterBkg':'#fff','clusterBorder':'#000','edgeLabelBackground':'#fff'}}}%%
graph TB
Client([Client with<br/>X.509 Certificate])
subgraph "gateway-system namespace"
GW[Gateway<br/>mtls-gateway]
TP[TLSPolicy<br/>mtls-gateway-tls]
CACM[(ConfigMap<br/>client-ca-bundle)]
TLSCERT[(Secret<br/>gateway-tls-cert)]
end
subgraph "default namespace"
HR[HTTPRoute<br/>httpbin-route]
AP[AuthPolicy<br/>x509-auth-policy]
SVC[Service<br/>httpbin]
POD[Deployment<br/>httpbin]
end
subgraph "kuadrant-system namespace"
CASEC[(Secret<br/>trusted-client-ca)]
end
Client -->|"1. mTLS handshake<br/>(client cert)"| GW
TP -.->|attached to| GW
TP -.->|manages cert| TLSCERT
GW -.->|validates against| CACM
GW -.->|server cert| TLSCERT
GW -->|"2. XFCC header"| HR
HR -->|routes to| SVC
SVC --> POD
AP -.->|attached to| HR
AP -.->|validates cert via<br/>label selector| CASEC
style GW fill:none,stroke:#000,color:#000
style TP fill:none,stroke:#000,color:#000
style HR fill:none,stroke:#000,color:#000
style AP fill:none,stroke:#000,color:#000
style CACM fill:none,stroke:#000,color:#000
style CASEC fill:none,stroke:#000,color:#000
style TLSCERT fill:none,stroke:#000,color:#000
style SVC fill:none,stroke:#000,color:#000
style POD fill:none,stroke:#000,color:#000
style Client fill:none,stroke:#000,color:#000
Component Interactions:
- TLS Layer (L4): Client presents certificate → Gateway validates against
client-ca-bundleConfigMap - Application Layer (L7): Gateway forwards XFCC header → AuthPolicy validates against
trusted-client-caSecret using label selectors - Routing: HTTPRoute connects the Gateway listener to the httpbin Service
- Policy Attachment: AuthPolicy attaches to HTTPRoute for fine-grained certificate validation
Gateway Configuration¶
The Gateway uses spec.tls.frontend.default.validation for client certificate validation:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: mtls-gateway
namespace: gateway-system
spec:
gatewayClassName: istio
listeners:
- name: https
protocol: HTTPS
port: 443
hostname: "*.nip.io"
tls:
mode: Terminate
certificateRefs:
- name: gateway-tls-cert
kind: Secret
tls:
frontend:
default:
validation:
caCertificateRefs:
- name: client-ca-bundle
kind: ConfigMap
group: ""
mode: AllowValidOnly # Requires valid client certificates
Key fields:
caCertificateRefs: References ConfigMap(s) containing trusted CA certificates in PEM formatmode: AllowValidOnly: Requires valid certificates; useAllowInsecureFallbackfor optional mTLS
AuthPolicy Configuration¶
The AuthPolicy configures X.509 authentication using the XFCC header:
apiVersion: kuadrant.io/v1
kind: AuthPolicy
metadata:
name: x509-auth-policy
namespace: default
spec:
targetRef:
group: gateway.networking.k8s.io
kind: HTTPRoute
name: httpbin-route
rules:
# Authentication based on client certificate
authentication:
"x509-client-cert":
x509:
# Extract certificate from XFCC header
source:
xfccHeader: "x-forwarded-client-cert"
# Label selector for trusted CA certificates
selector:
matchLabels:
app.kubernetes.io/name: trusted-client
# Additional rules to enforce authorization based on certificate attributes
# and propagate client certificate information into request headers
authorization:
"certificate-attributes":
patternMatching:
patterns:
- predicate: "size(auth.identity.Organization) > 0 && auth.identity.Organization[0] == 'Kuadrant'"
response:
success:
headers:
# Extract the Common Name (CN) from the certificate
"x-client-cn":
plain:
expression: auth.identity.CommonName
# Extract the Organization (O) from the certificate
"x-client-org":
plain:
expression: auth.identity.Organization[0]
Certificate Source Options:
The x509.source field supports three options:
-
xfccHeader: Extract from Envoy's X-Forwarded-Client-Cert header -
clientCertHeader: Extract from RFC 9440 Client-Cert header -
expression: Extract using CEL expression for custom attribute paths
Testing¶
Test with Valid Certificate¶
GATEWAY_IP=$(kubectl get gateway mtls-gateway -n gateway-system -o jsonpath='{.status.addresses[0].value}')
# Make request with client certificate
curl -ik https://httpbin.$GATEWAY_IP.nip.io/get \
--cert /tmp/client.crt \
--key /tmp/client.key
Expected Result: Request succeeds (HTTP 200) because both gateway and Authorino validation pass.
Test without Certificate¶
Expected Result: TLS handshake fails. Gateway rejects connection because no client certificate was provided.
Test with Invalid Certificate¶
# Generate a self-signed certificate not trusted by the CA
openssl req -x509 -newkey rsa:2048 -nodes \
-keyout /tmp/untrusted.key -out /tmp/untrusted.crt -days 365 \
-subj "/CN=untrusted-client/O=Untrusted/C=US"
# Try to connect with untrusted certificate
curl -ik https://httpbin.$GATEWAY_IP.nip.io/get \
--cert /tmp/untrusted.crt \
--key /tmp/untrusted.key
Expected Result: TLS handshake fails. Gateway rejects connection because the certificate is not signed by a trusted CA.
Test with Unauthorized Certificate¶
# Generate a CA-signed certificate with different attributes that do not match the AuthPolicy rules
openssl genrsa -out /tmp/unauthorized-client.key 4096
openssl req -new \
-subj "/CN=unauthorized-client/O=Unauthorized/C=US" \
-key /tmp/unauthorized-client.key \
-out /tmp/unauthorized-client.csr
openssl x509 -req -sha512 \
-days 1 \
-CA /tmp/ca.crt \
-CAkey /tmp/ca.key \
-CAcreateserial \
-extfile /tmp/x509v3.ext \
-in /tmp/unauthorized-client.csr \
-out /tmp/unauthorized-client.crt
# Try to connect with unauthorized certificate
curl -ik https://httpbin.$GATEWAY_IP.nip.io/get \
--cert /tmp/unauthorized-client.crt \
--key /tmp/unauthorized-client.key
Expected Result: Request fails (HTTP 403) because Authorino rejects the certificate based on unauthorized attribute (Organization).