Skip to content

X.509 Client Certificate Authentication

Verify cryptographic identity with client certificates

When your organization requires strong cryptographic identity verification—such as for zero-trust architectures, compliance requirements, or machine-to-machine communication with PKI—X.509 client certificate authentication provides defense-in-depth security with validation at both the TLS layer and application layer.

When to use X.509 authentication

Choose X.509 client certificate authentication when:

  • Zero-trust architecture: You need cryptographic proof of identity for every request
  • Compliance requirements: Security standards (PCI DSS, HIPAA, FedRAMP) mandate mutual TLS (mTLS)
  • Machine-to-machine communication: Services authenticate with long-lived certificates from your PKI
  • Multi-CA trust: Different clients are issued certificates by different CAs, and you need fine-grained control over which CAs are trusted for which routes
  • No shared secrets: You want to avoid the operational burden of rotating and distributing API keys or tokens

How X.509 authentication works in Kuadrant

Kuadrant's X.509 authentication implements a two-layer validation model for defense-in-depth security:

Layer 1 (TLS/L4): The Gateway validates client certificates during the TLS handshake against CA certificates configured in the Gateway resource. This cryptographically verifies that the client possesses the private key and that the certificate is trusted.

Layer 2 (Application/L7): Authorino validates certificates extracted from the X-Forwarded-Client-Cert (XFCC) header using label selectors to support multi-CA trust and fine-grained validation rules.

Both layers must succeed for the request to be authenticated. This defense-in-depth approach provides:

  • Security: Invalid, expired, or untrusted certificates are rejected at the TLS layer before reaching the application
  • Flexibility: Application-layer validation supports multi-CA trust scenarios where different CAs are trusted for different routes or services
  • Auditability: Certificate attributes (CN, Organization, etc.) are available for authorization decisions and request enrichment

Configuration tiers

Kuadrant supports three configuration tiers for X.509 authentication, each with different security characteristics and requirements. Choose the tier that matches your Gateway API version, gateway provider, and security requirements.

Use Tier 1 when: You have Gateway API v1.5+ and a compatible gateway implementation (Istio v1.28+, Envoy Gateway with v1.5 support).

Tier 1 uses the standard Gateway API spec.tls.frontend.default.validation field to configure client certificate validation at the Gateway level, providing automatic defense-in-depth security.

Security characteristics:

  • ✅ Defense-in-depth: Both L4 (TLS) and L7 (Authorino) validation
  • ✅ Cryptographic proof of private key possession
  • ✅ Standard Gateway API configuration
  • ✅ XFCC header automatically set by gateway after successful TLS validation
  • ✅ Incoming XFCC headers automatically stripped to prevent spoofing

Gateway configuration:

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: "*.example.com"
    tls:
      mode: Terminate
      certificateRefs:
      - name: gateway-tls-cert
  # Frontend TLS validation for client certificates
  tls:
    frontend:
      default:
        validation:
          # Reference ConfigMap(s) with trusted CA certificates
          caCertificateRefs:
          - name: client-ca-bundle
            kind: ConfigMap
          # Require valid client certificates
          mode: AllowValidOnly  # or AllowInsecureFallback for optional mTLS

AuthPolicy configuration:

apiVersion: kuadrant.io/v1
kind: AuthPolicy
metadata:
  name: x509-auth
spec:
  targetRef:
    group: gateway.networking.k8s.io
    kind: HTTPRoute
    name: protected-api
  rules:
    authentication:
      "client-certificate":
        x509:
          # Extract certificate from XFCC header
          source:
            xfccHeader: "x-forwarded-client-cert" # cert in envoy XFCC text format, extracted from the header
          # Select trusted CA certificates using labels
          selector:
            matchLabels:
              app.kubernetes.io/name: trusted-client

See the X.509 Client Certificate Authentication user guide for a complete working example.

Tier 2: Provider-specific resources (Alternative for older Gateway API versions)

Use Tier 2 when: You don't have Gateway API v1.5+ but still want defense-in-depth security with L4 TLS validation.

Tier 2 uses provider-specific resources to configure TLS validation at the gateway level:

  • Istio: EnvoyFilter resources
  • Envoy Gateway: EnvoyPatchPolicy resources

Security characteristics:

  • ✅ Defense-in-depth: Both L4 (TLS) and L7 (Authorino) validation
  • ✅ Cryptographic proof of private key possession
  • ✅ XFCC header set by gateway after successful TLS validation
  • ⚠️ Requires provider-specific configuration knowledge
  • ⚠️ Configuration varies by gateway provider

Trade-offs:

  • More complex configuration compared to Tier 1
  • Provider-specific knowledge required
  • Same security guarantees as Tier 1

When to migrate to Tier 1: When you upgrade to Gateway API v1.5+ and your gateway provider supports frontend TLS validation, migrate from Tier 2 to Tier 1 for standardized configuration.

Tier 3: Certificate in request header only (Exceptional cases)

Use Tier 3 only when: Gateway-level TLS validation is not feasible, and you have a trusted upstream proxy that performs TLS validation and populates the XFCC or Client-Cert header.

Tier 3 extracts certificates from request headers without gateway-level TLS validation. Authorino becomes the sole certificate validator.

Security characteristics:

  • ❌ No defense-in-depth (single validation point at L7)
  • ❌ No cryptographic proof of private key possession
  • ❌ Potential spoofing risk if headers originate from untrusted sources
  • ✅ Still validates certificate chain against trusted CAs
  • ✅ Supports multi-CA trust via label selectors

When Tier 3 is acceptable:

  • Certificate header originates from a trusted upstream proxy that performed TLS validation
  • Network topology prevents direct client access to the gateway
  • You understand and accept the security trade-offs

AuthPolicy configuration with Client-Cert header (RFC 9440):

apiVersion: kuadrant.io/v1
kind: AuthPolicy
metadata:
  name: x509-header-only
spec:
  targetRef:
    group: gateway.networking.k8s.io
    kind: HTTPRoute
    name: api-route
  rules:
    authentication:
      "client-cert-header":
        x509:
          # Extract certificate from RFC 9440 Client-Cert header
          source:
            clientCertHeader: "client-cert" # cert in der format, base64-encoded, extracted from the header
          selector:
            matchLabels:
              tier: trusted-upstream

⚠️ Important: Use Tier 1 or Tier 2 whenever possible. Only use Tier 3 in exceptional cases where gateway-level TLS validation is not feasible and you have a trusted upstream proxy.

Certificate source options

The x509.source field specifies where to extract the client certificate from:

Source Value When to use Security
xfccHeader Name of the Envoy X-Forwarded-Client-Cert header Istio, Envoy Gateway (Tier 1 or 2) ✅ Set by gateway after TLS validation
clientCertHeader Name of the RFC 9440 Client-Cert header Trusted upstream proxy implementing RFC 9440 (Tier 3) ⚠️ Requires trusted source
expression CEL expression that evaluates to a certificate Custom header names or attribute paths (Tier 3) ⚠️ Requires trusted source

Example with custom CEL expression:

x509:
  source:
    expression: 'request.http.headers["x-custom-client-cert"]' # cert in pem format, URL-encoded, extracted from a custom header using CEL
  selector:
    matchLabels:
      app: my-service

Multi-CA trust with label selectors

Unlike traditional proxy-based client certificate validation that supports only a single CA bundle, Kuadrant's X.509 authentication allows you to trust multiple CAs and select different CA sets for different routes using Kubernetes label selectors.

Example - Trust CA certificates labeled with specific environment:

x509:
  selector:
    matchLabels:
      environment: production
      tier: customer-facing

Example - More complex selectors:

x509:
  selector:
    matchExpressions:

    - key: ca-tier
      operator: In
      values: ["tier-1", "tier-2"]
    - key: deprecated
      operator: DoesNotExist

This enables sophisticated trust models such as:

  • Different CAs for production vs. staging environments
  • Separate CAs for different business units or customers
  • Gradual CA rotation by trusting both old and new CAs during migration
  • Per-route CA trust policies for multi-tenant scenarios

CA Secret requirements:

Trusted CA certificates must be stored in Kubernetes TLS secrets in the Kuadrant namespace with appropriate labels:

kubectl create secret tls customer-a-ca \
  -n kuadrant-system \
  --cert=customer-a-ca.crt \
  --key=customer-a-ca.key

kubectl label secret customer-a-ca \
  -n kuadrant-system \
  authorino.kuadrant.io/managed-by=authorino \
  customer=customer-a \
  environment=production

Cross-namespace trust

By default, Authorino only trusts CA secrets in the Kuadrant namespace. To allow cross-namespace references, set allNamespaces: true in the AuthPolicy, but use this with caution as it expands the trust scope.

Certificate requirements and constraints

Required certificate attributes

Client certificates must meet these requirements for Authorino validation to succeed:

  1. Extended Key Usage (EKU): Certificates must include the x509 v3 extension specifying Client Authentication (1.3.6.1.5.5.7.3.2) extended key usage

Example certificate generation with Client Auth EKU:

# Create X.509 v3 extensions file
cat > client-cert-ext.cnf << EOF
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage=digitalSignature,nonRepudiation,keyEncipherment,dataEncipherment
extendedKeyUsage=clientAuth
EOF

# Generate certificate with Client Auth EKU
openssl x509 -req -sha256 \
  -days 365 \
  -CA ca.crt -CAkey ca.key \
  -extfile client-cert-ext.cnf \
  -in client.csr -out client.crt

  1. Valid certificate chain: Certificate must chain to a trusted CA configured in Authorino secrets

  2. Not expired: Certificate must be within its validity period

  3. Proper encoding:

  4. XFCC header: Envoy XFCC text format
  5. Client-Cert header (RFC 9440): DER format, base64-encoded
  6. CEL Expression: PEM-encoded, URL-encoded

Certificate identity object

When X.509 authentication succeeds, the identity object (auth.identity) contains the certificate subject fields available for use in authorization rules and response transformations:

Field Type OID Description Example
CommonName string 2.5.4.3 Common Name (CN) "api-client.example.com"
Country array of strings 2.5.4.6 Country (C) ["US"]
Organization array of strings 2.5.4.10 Organization (O) ["ACME Corp"]
OrganizationalUnit array of strings 2.5.4.11 Organizational Unit (OU) ["Engineering", "Platform"]
Locality array of strings 2.5.4.7 Locality (L) ["San Francisco"]
Province array of strings 2.5.4.8 Province/State (ST) ["California"]
StreetAddress array of strings 2.5.4.9 Street Address ["123 Main St"]
PostalCode array of strings 2.5.4.17 Postal Code ["94105"]
SerialNumber string 2.5.4.5 Subject serial number "01:23:45:67:89:AB:CD:EF"

Example - Use certificate attributes in authorization:

authorization:
  "verify-organization":
    patternMatching:
      patterns:

      - predicate: "auth.identity.Organization[0] == 'ACME Corp'"
      - predicate: "'Engineering' in auth.identity.OrganizationalUnit"

Example - Inject certificate attributes into request headers:

response:
  success:
    headers:
      "x-client-cn":
        plain:
          expression: auth.identity.CommonName
      "x-client-org":
        plain:
          expression: auth.identity.Organization[0]
      "x-client-subject":
        json:
          properties:
            cn:
              expression: auth.identity.CommonName
            org:
              expression: auth.identity.Organization[0]
            ou:
              expression: auth.identity.OrganizationalUnit

Security considerations

XFCC header security

Critical requirement: The XFCC header must originate from a trusted source to prevent spoofing.

Tier 1 & 2: XFCC set by gateway after validation ✅

With gateway-level TLS validation, the proxy must be configured to:

  1. Strip incoming XFCC headers: Any XFCC from incoming requests must be removed to prevent client spoofing
  2. Set XFCC only after validation: The proxy sets the XFCC header exclusively after successful TLS validation

Envoy behavior with forward_client_cert_details: SANITIZE_SET (recommended):

  • Strips any incoming XFCC headers from untrusted clients
  • Sets the XFCC header only after TLS validation succeeds
  • Provides cryptographic proof that the client possesses the private key

Verification: Ensure your Gateway configuration uses the recommended Envoy settings. For Tier 1 (Gateway API v1.5+), this is automatic. For Tier 2, verify your EnvoyFilter or provider-specific resource configuration.

Tier 3: XFCC forwarded from client ⚠️

With Tier 3 configuration, the proxy forwards the XFCC or Client-Cert header from the incoming request without validation.

Risks:

  • Potential client spoofing of the certificate header
  • No cryptographic proof of private key possession
  • Single validation point (Authorino only)

Acceptable scenarios:

  • Certificate header originates from a trusted upstream proxy that performed TLS validation
  • Network topology prevents direct client access to the gateway
  • You understand and accept the L7-only validation trade-offs

⚠️ Warning: Do not use Tier 3 configuration unless you trust the source of the certificate header and understand the security implications. When in doubt, use Tier 1 or Tier 2.

CA certificate management

Proper CA certificate management is critical for maintaining security:

Gateway-level CA configuration (Tier 1 & 2)

Responsibilities:

  • Securely manage CA certificates for gateway validation
  • Tier 1: ConfigMaps referenced in Gateway spec.tls.frontend.default.validation.caCertificateRefs
  • Tier 2: CA certificates in EnvoyFilter or provider-specific resources
  • Certificate rotation requires ConfigMap updates and potential gateway pod restarts
  • Namespace isolation through ReferenceGrant controls cross-namespace references

Authorino-level CA configuration (All tiers)

Responsibilities:

  • Manage CA certificates in Kubernetes TLS secrets with appropriate labels
  • Rotation requires updating secrets (Authorino watches for changes)
  • Multi-CA trust via label selectors enables fine-grained revocation control
  • Secrets must be in the Kuadrant namespace preferably (or use allNamespaces: true with caution)

Key distinction: Gateway CAs typically represent a single root CA validated at TLS handshake, while Authorino CAs can represent multiple intermediate CAs selected via labels, enabling granular trust management.

Best practices

  1. Use Tier 1 whenever possible: Gateway API v1.5+ frontend TLS validation provides standardized, secure configuration

  2. Automate certificate management: Use cert-manager for automatic CA certificate provisioning and rotation at both gateway and Authorino levels

  3. Implement certificate rotation strategies:

  4. Overlap periods where both old and new CAs are trusted
  5. Use label selectors to gradually phase out old CAs
  6. Monitor certificate expiration with alerting

  7. Monitor certificate health:

  8. Track certificate expiration timelines
  9. Alert on approaching expiration (30, 14, 7 days)
  10. Monitor authentication failure rates

  11. Consider separate CAs for different trust levels:

  12. Gateway-level: Broad trust (all certificates from organizational CA)
  13. Authorino-level: Granular trust (specific CAs per route/service)

  14. Validate Client Auth EKU: Ensure all client certificates include the extendedKeyUsage=clientAuth extension

  15. Header size considerations:

  16. Certificate chains in XFCC headers can exceed typical header limits
  17. Consider using shorter certificate chains when possible
  18. Monitor for header size-related failures

  19. Network segmentation: For Tier 3 configurations, ensure network topology prevents direct client access to the gateway

Troubleshooting

Authentication fails with "invalid certificate"

Possible causes:

  • Certificate doesn't chain to a trusted CA
  • Certificate is expired
  • Certificate doesn't have Client Authentication EKU
  • CA secret not labeled correctly or not in Kuadrant namespace

Resolution:

# Verify certificate has Client Auth EKU
openssl x509 -in client.crt -noout -text | grep "TLS Web Client Authentication"

# Check certificate validity
openssl x509 -in client.crt -noout -dates

# Verify CA secret exists and has correct labels
kubectl get secret -n kuadrant-system -l authorino.kuadrant.io/managed-by=authorino

# Check Authorino logs
kubectl logs -n kuadrant-system -l authorino-resource-uid=<uid> | grep x509

XFCC header not populated

Possible causes:

  • Gateway not configured for frontend TLS validation (Tier 1)
  • EnvoyFilter not applied correctly (Tier 2)
  • Client didn't present certificate during TLS handshake

Resolution:

# For Tier 1: Check Gateway configuration
kubectl get gateway <gateway-name> -o yaml | grep -A 10 frontend

# Check if XFCC is in request (from Authorino perspective)
kubectl logs -n kuadrant-system -l authorino-resource-uid=<uid> | grep -i xfcc

# Test TLS handshake
openssl s_client -connect gateway.example.com:443 \
  -cert client.crt -key client.key -CAfile ca.crt

Multi-CA trust not working

Possible causes:

  • CA secrets don't have required labels
  • Label selectors don't match any CA secrets
  • Secrets in wrong namespace

Resolution:

# List CA secrets with labels
kubectl get secrets -n kuadrant-system \
  -l authorino.kuadrant.io/managed-by=authorino \
  --show-labels

# Verify selector matches secrets
kubectl get secrets -n kuadrant-system \
  -l app.kubernetes.io/name=trusted-client

# Check AuthPolicy selector configuration
kubectl get authpolicy <policy-name> -o yaml | grep -A 5 selector

See also