Configure mTLS Between Client and Gateway
Learn how to use APISIX or API7 Ingress Controller to configure the gateway to require mutual TLS (mTLS) from clients.
Applies to:API7 Ingress Controller (drives API7 Gateway)APISIX Ingress Controller (drives Apache APISIX)
Goal
Configure the gateway to require clients to present a trusted certificate for mutual TLS (mTLS) authentication.
Before you begin
- Complete Set Up Ingress Controller and Gateway.
- You have
kubectlconfigured against the cluster used in the setup guide. - You have OpenSSL installed to create the test CA, server, and client certificates.
- Choose a free local port for the gateway HTTPS port-forward. The examples use
9443.
Set the variables this guide uses:
export TLS_SNI="test.com"
export GATEWAY_SERVICE_NAME="apisix-gateway" # your gateway's Service name in namespace aic
export GATEWAY_HTTPS_PORT="9443"Steps
Step 1: Enable SSL on the gateway
Ensure that your gateway has enabled SSL.
helm upgrade apisix apisix/apisix \
--set "apisix.ssl.enabled=true" \
--set "apisix.ssl.containerPort=9443"By default, API7 Gateway enables SSL on container port 9443 and service port 443, so no additional configuration is required for this step.
Step 2: Generate the test CA and certificates
Generate the Certificate Authority (CA) key and certificate:
openssl genrsa -out ca.key 2048
openssl req -new -x509 -days 36500 -sha256 \
-key ca.key \
-out ca.crt \
-subj "/CN=MyTestCA" \
-extensions v3_ca \
-config <(printf "[req]\ndistinguished_name=req\n[ v3_ca ]\nbasicConstraints=critical,CA:TRUE\nkeyUsage=critical,keyCertSign,cRLSign\nsubjectKeyIdentifier=hash\nauthorityKeyIdentifier=keyid:always,issuer")Generate the server key and certificate signing request (CSR), then sign it with the CA:
openssl genrsa -out server.key 2048
openssl req -new -sha256 \
-key server.key \
-out server.csr \
-subj "/CN=${TLS_SNI}"
openssl x509 -req -days 36500 -sha256 \
-in server.csr \
-CA ca.crt -CAkey ca.key -CAcreateserial \
-out server.crt \
-extensions v3_req \
-extfile <(printf "[v3_req]\nbasicConstraints=CA:FALSE\nkeyUsage=digitalSignature,keyEncipherment\nextendedKeyUsage=serverAuth\nsubjectAltName=DNS:%s" "${TLS_SNI}")Generate the client key and CSR, then sign it with the CA:
openssl genrsa -out client.key 2048
openssl req -new -sha256 \
-key client.key \
-out client.csr \
-subj "/CN=CLIENT"
openssl x509 -req -days 36500 -sha256 \
-in client.csr \
-CA ca.crt -CAkey ca.key -CAcreateserial \
-out client.crt \
-extensions v3_req \
-extfile <(printf "[v3_req]\nbasicConstraints=CA:FALSE\nkeyUsage=digitalSignature,keyEncipherment\nextendedKeyUsage=clientAuth")Step 3: Configure mTLS for the gateway
Create the server certificate Secret and a ConfigMap containing the client CA certificate. Gateway API reads CA certificates from the ca.crt key:
kubectl create secret tls test-mtls-secret \
--cert=server.crt \
--key=server.key \
--namespace=aic
kubectl create configmap test-client-ca \
--from-file=ca.crt=ca.crt \
--namespace=aicCreate a Gateway that requires clients to present a certificate signed by the CA. spec.tls.frontend.default applies the validation configuration to all HTTPS listeners unless a listener port has an entry under spec.tls.frontend.perPort:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: apisix
namespace: aic
spec:
gatewayClassName: apisix
tls:
frontend:
default:
validation:
caCertificateRefs:
- group: ""
kind: ConfigMap
name: test-client-ca
listeners:
- name: https
protocol: HTTPS
port: 443
hostname: test.com
tls:
mode: Terminate
certificateRefs:
- group: ""
kind: Secret
name: test-mtls-secret
infrastructure:
parametersRef:
group: apisix.apache.org
kind: GatewayProxy
name: apisix-configApply the configuration:
kubectl apply -f mtls-gateway.yamlThe Ingress Controller also accepts a Secret in caCertificateRefs as an implementation-specific extension. A cross-namespace CA reference requires a ReferenceGrant. AllowInsecureFallback is not supported because the gateway cannot accept clients that omit a certificate or fail certificate validation.
Create two Kubernetes secrets:
kubectl create secret tls test-mtls-secret \
--cert=server.crt \
--key=server.key \
--namespace=aic
kubectl create secret generic test-ca-secret \
--from-file=cert=ca.crt \
--namespace=aicCreate a Kubernetes manifest file for mTLS configuration. The example uses the SNI test.com:
apiVersion: apisix.apache.org/v2
kind: ApisixTls
metadata:
namespace: aic
name: test-mtls
spec:
ingressClassName: apisix
hosts:
- test.com
secret:
name: test-mtls-secret
namespace: aic
client:
caSecret:
name: test-ca-secret
namespace: aic
depth: 1Apply the configuration to your cluster:
kubectl apply -f mtls.yamlStep 4: Create a route
Create an ExternalName Service and HTTPRoute that forward /ip requests to httpbin.org:
apiVersion: v1
kind: Service
metadata:
name: httpbin-external-domain
namespace: aic
spec:
type: ExternalName
externalName: httpbin.org
ports:
- name: http
port: 80
appProtocol: http
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: httpbin-ip
namespace: aic
spec:
parentRefs:
- name: apisix
sectionName: https
hostnames:
- test.com
rules:
- matches:
- path:
type: PathPrefix
value: /ip
backendRefs:
- name: httpbin-external-domain
port: 80This example follows the Getting Started configuration with listener-port matching disabled, so the route can match when Service port 443 maps to container port 9443. If you enable matching, review the effective mode and physical gateway ports in the configuration reference.
Apply the configuration:
kubectl apply -f httpbin-route.yamlCreate an example route that forwards all requests with the path /ip to the upstream service httpbin.org:
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: httpbin-external-domain
spec:
ingressClassName: apisix
externalNodes:
- type: Domain
name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: httpbin-ip
spec:
ingressClassName: apisix
http:
- name: httpbin-ip
match:
paths:
- /ip
upstreams:
- name: httpbin-external-domainApply the configuration to your cluster:
kubectl apply -f httpbin-route.yamlValidate
Forward the gateway's SSL port to your local machine:
kubectl port-forward -n aic "svc/${GATEWAY_SERVICE_NAME}" "${GATEWAY_HTTPS_PORT}:443" &With client certificate
Since the certificate is valid for ${TLS_SNI}, use ${TLS_SNI} as the gateway's domain name. Send a request to the route with the client certificate:
curl -iv --cacert ca.crt \
--resolve "${TLS_SNI}:${GATEWAY_HTTPS_PORT}:127.0.0.1" "https://${TLS_SNI}:${GATEWAY_HTTPS_PORT}/ip" \
--cert client.crt --key client.keyExpected: an mTLS handshake completes and the response contains HTTP/2 200.
Without client certificate
Send a request to the route without a client certificate:
curl -iv --cacert ca.crt \
--resolve "${TLS_SNI}:${GATEWAY_HTTPS_PORT}:127.0.0.1" "https://${TLS_SNI}:${GATEWAY_HTTPS_PORT}/ip"Expected: the handshake fails because the gateway rejects the client that does not present a trusted certificate.
Roll back / Clean up
Delete the manifests, secrets, and ConfigMap created in this guide:
kubectl delete -f httpbin-route.yaml
kubectl delete -f mtls-gateway.yaml
kubectl delete -f mtls.yaml
kubectl delete secret test-mtls-secret -n aic
kubectl delete configmap test-client-ca -n aic
kubectl delete secret test-ca-secret -n aic
rm -f ca.key ca.crt ca.srl server.key server.csr server.crt client.key client.csr client.crtStop the local port-forward process if it is still running:
kill %1 2>/dev/null || trueTroubleshoot
| Symptom | Cause | Fix |
|---|---|---|
curl reports a certificate error when using the client certificate. | The server certificate CN does not match the requested SNI, or the client certificate is not signed by the CA trusted by the gateway. | Confirm TLS_SNI is the value used in the OpenSSL -subj and the Gateway/ApisixTls hosts/hostname, and that the client CSR was signed by the same CA configured in test-client-ca or test-ca-secret. |
curl succeeds without a client certificate. | Client certificate validation is not enabled, or the Gateway/ApisixTls resource is not reconciled. | Verify the Gateway has tls.frontend.default.validation.caCertificateRefs (Gateway API) or client.caSecret (APISIX CRD), and that the route parentRefs resolve. |
The route returns 404. | The HTTPRoute or ApisixRoute is not reconciled, or it does not match the requested path. | Check that the route parentRefs resolve and that the path is /ip. |