feat: ApisixTls support mTLS (#492)
diff --git a/.licenserc.yaml b/.licenserc.yaml
index ad8836c..070b515 100644
--- a/.licenserc.yaml
+++ b/.licenserc.yaml
@@ -38,4 +38,6 @@
- 'pkg/kube/apisix/client/**'
- '**/zz_generated.deepcopy.go'
- 'utils/generate-groups.sh'
+ - '**/*.pem'
+ - '**/*.key'
comment: on-failure
diff --git a/docs/en/latest/practices/mtls.md b/docs/en/latest/practices/mtls.md
new file mode 100644
index 0000000..7651bb4
--- /dev/null
+++ b/docs/en/latest/practices/mtls.md
@@ -0,0 +1,222 @@
+---
+title: Configuring Mutual Authentication via ApisixTls
+---
+
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+-->
+
+In this practice, we will use mTLS to protect our exposed ingress APIs.
+
+To learn more about mTLS, please refer to [Mutual authentication](https://en.wikipedia.org/wiki/Mutual_authentication)
+
+## Prerequisites
+
+- an available Kubernetes cluster
+- an available APISIX and APISIX Ingress Controller installation
+
+In this guide, we assume that your APISIX is installed in the `apisix` namespace and `ssl` is enabled, which is not enabled by default in the Helm Chart. To enable it, you need to set `gateway.tls.enabled=true` during installation.
+
+Assuming the SSL port is `9443`.
+
+## Deploy httpbin service
+
+We use [kennethreitz/httpbin](https://hub.docker.com/r/kennethreitz/httpbin/) as the service image, See its overview page for details.
+
+Deploy it to the default namespace:
+
+```shell
+kubectl run httpbin --image kennethreitz/httpbin --port 80
+kubectl expose pod httpbin --port 80
+```
+
+## Route the traffic
+
+Since SSL is not configured in ApisixRoute, we can use the config similar to the one in practice [Proxy the httpbin service](./proxy-the-httpbin-service.md).
+
+```yaml
+# route.yaml
+apiVersion: apisix.apache.org/v2alpha1
+kind: ApisixRoute
+metadata:
+ name: httpserver-route
+spec:
+ http:
+ - name: httpbin
+ match:
+ hosts:
+ - mtls.httpbin.local
+ paths:
+ - "/*"
+ backend:
+ serviceName: httpbin
+ servicePort: 80
+```
+
+Please remember the host field is `mtls.httpbin.local`. It will be the domain we are going to use.
+
+Test it:
+
+```bash
+kubectl -n apisix exec -it <APISIX_POD_NAME> -- curl "http://127.0.0.1:9080/ip" -H "Host: mtls.httpbin.local"
+```
+
+It should output:
+
+```json
+{
+ "origin": "127.0.0.1"
+}
+```
+
+## Certificates
+
+Before configuring SSL, we must have certificates. Certificates often authorized by certificate provider, which also known as Certification Authority (CA).
+
+You can use [OpenSSL](https://en.wikipedia.org/wiki/Openssl) to generate self-signed certificates for testing purposes. Some pre-generated certificates for this guide are [here](./mtls).
+
+- `ca.pem`: The root CA.
+- `server.pem` and `server.key`: Server certificate used to enable SSL (https). Contains correct `subjectAltName` matches domain `mtls.httpbin.local`.
+- `user.pem` and `user.key`: Client certificate.
+
+To verify them, use commands below:
+
+```bash
+openssl verify -CAfile ./ca.pem ./server.pem
+openssl verify -CAfile ./ca.pem ./user.pem
+```
+
+## Protect the route using SSL
+
+In APISIX Ingress Controller, we use [ApisixTls](../concepts/apisix_tls.md) resource to protect our routes.
+
+ApisixTls requires a secret which field `cert` and `key` contains the certificate and private key.
+
+A secret yaml containing the certificate mentioned above [is here](./mtls/server-secret.yaml). In this guide, we use this as an example.
+
+```bash
+kubectl apply -f ./mtls/server-secret.yaml -n default
+```
+
+The secret name is `server-secret`, we created it in the `default` namespace. We will reference this secret in `ApisixTls`.
+
+```yaml
+# tls.yaml
+apiVersion: apisix.apache.org/v1
+kind: ApisixTls
+metadata:
+ name: sample-tls
+spec:
+ hosts:
+ - mtls.httpbin.local
+ secret:
+ name: server-secret
+ namespace: default
+```
+
+The `secret` field contains the secret reference.
+
+Please note that the `hosts` field matches our domain `mtls.httpbin.local`.
+
+Apply this yaml, APISIX Ingress Controller will use our certificate to protect the route. Let's test it.
+
+```bash
+kubectl -n apisix exec -it <APISIX_POD_NAME> -- curl --resolve 'mtls.httpbin.local:9443:127.0.0.1' "https://mtls.httpbin.local:9443/ip" -k
+```
+
+Some major changes here:
+
+- Use `--resolve` parameter to resolve our domain.
+ - No `Host` header set explicit.
+- We are using `https` and SSL port `9443`.
+- Parameter `-k` to allow insecure connections when using SSL. Because our self-signed certificate is not trusted.
+
+Without the domain `mtls.httpbin.local`, the request won't succeed.
+
+You can add parameter `-v` to log the handshake process.
+
+Now, we configured SSL successfully.
+
+## Mutual Authentication
+
+Like `server-secret`, we will create a `client-ca-secret` to store the CA that verify the certificate client presents.
+
+```bash
+kubectl apply -f ./mtls/client-ca-secret.yaml -n default
+```
+
+Then, change our ApisixTls and apply it:
+
+```yaml
+# mtls.yaml
+apiVersion: apisix.apache.org/v1
+kind: ApisixTls
+metadata:
+ name: sample-tls
+spec:
+ hosts:
+ - mtls.httpbin.local
+ secret:
+ name: server-secret
+ namespace: default
+ client:
+ caSecret:
+ name: client-ca-secret
+ namespace: default
+ depth: 10
+```
+
+The `client` field references the secret, `depth` indicates the max certificate chain length.
+
+Let's try to connect the route without any chanegs:
+
+```bash
+kubectl -n apisix exec -it <APISIX_POD_NAME> -- curl --resolve 'mtls.httpbin.local:9443:127.0.0.1' "https://mtls.httpbin.local:9443/ip" -k
+```
+
+If everything works properly, it will return a `400 Bad Request`.
+
+From APISIX access log, we could find logs like this:
+
+```log
+2021/05/27 17:20:54 [error] 43#43: *106132 [lua] init.lua:293: http_access_phase(): client certificate was not present, client: 127.0.0.1, server: _, request: "GET /ip HTTP/2.0", host: "mtls.httpbin.local:9443"
+127.0.0.1 - - [27/May/2021:17:20:54 +0000] mtls.httpbin.local:9443 "GET /ip HTTP/2.0" 400 154 0.000 "-" "curl/7.76.1" - - - "http://mtls.httpbin.local:9443"
+```
+
+That means our mutual authentication has been enabled successfully.
+
+Now, we need to transfer our client cert to the APISIX container to verify the mTLS functionality.
+
+```bash
+# Transfer client certificate
+kubectl -n apisix cp ./user.key <APISIX_POD_NAME>:/tmp/user.key
+kubectl -n apisix cp ./user.pem <APISIX_POD_NAME>:/tmp/user.pem
+
+# Test
+kubectl -n apisix exec -it <APISIX_POD_NAME> -- curl --resolve 'mtls.httpbin.local:9443:127.0.0.1' "https://mtls.httpbin.local:9443/ip" -k --cert /tmp/user.pem --key /tmp/user.key
+```
+
+Parameter `--cert` and `--key` indicates our certificate and key path.
+
+It should output normally:
+
+```json
+{
+ "origin": "127.0.0.1"
+}
+```
diff --git a/docs/en/latest/practices/mtls/ca.pem b/docs/en/latest/practices/mtls/ca.pem
new file mode 100644
index 0000000..a0c9587
--- /dev/null
+++ b/docs/en/latest/practices/mtls/ca.pem
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF9zCCA9+gAwIBAgIUFKuzAJZgm/fsFS6JDrd+lcpVZr8wDQYJKoZIhvcNAQEL
+BQAwgZwxCzAJBgNVBAYTAkNOMREwDwYDVQQIDAhaaGVqaWFuZzERMA8GA1UEBwwI
+SGFuZ3pob3UxGDAWBgNVBAoMD0FQSVNJWC1UZXN0LUNBXzEYMBYGA1UECwwPQVBJ
+U0lYX0NBX1JPT1RfMRUwEwYDVQQDDAxBUElTSVguUk9PVF8xHDAaBgkqhkiG9w0B
+CQEWDXRlc3RAdGVzdC5jb20wHhcNMjEwNTI3MTMzNjI4WhcNMjIwNTI3MTMzNjI4
+WjCBnDELMAkGA1UEBhMCQ04xETAPBgNVBAgMCFpoZWppYW5nMREwDwYDVQQHDAhI
+YW5nemhvdTEYMBYGA1UECgwPQVBJU0lYLVRlc3QtQ0FfMRgwFgYDVQQLDA9BUElT
+SVhfQ0FfUk9PVF8xFTATBgNVBAMMDEFQSVNJWC5ST09UXzEcMBoGCSqGSIb3DQEJ
+ARYNdGVzdEB0ZXN0LmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
+ALJR0lQW/IBqQTE/Oa0Pi4LlmlYUSGnqtFNqiZyOF0PjVzNeqoD9JDPiM1QRyC8p
+NCd5L/QhtUIMMx0RlDI9DkJ3ALIWdrPIZlwpveDJf4KtW7cz+ea46A6QQwB6xcyV
+xWnqEBkiea7qrEE8NakZOMjgkqkN2/9klg6XyA5FWfvszxtuIHtjcy2Kq8bMC0jd
+k7CqEZe4ct6s2wlcI8t8s9prvMDm8gcX66x4Ah+C2/W+C3lTpMDgGqRqSPyCW7na
+Wgn0tWmTSf1iybwYMydhC+zpM1QJLvfDyqjp1wJhziR5ttVe2Xc+tDC24s+u16yZ
+R93IO0M4lLNjvEKJcMltXyRzrcjvLXOhw3KirSHNL1KfrBEl74lb+DV5eU4pIFCj
+cu18gms5FBYs9tpLujwpHDc2MU+zCvRmSPvUA4yCyoXqom3uiSo3g3ymW9IM8dC8
++Bd1GdM6JbpBukvQybc5TQXo1M75I9iEoQa5tQxAfQ/dfwMjOK7skogowBouOuLv
+BEFKy3Vd57IWWZXC4p/74M6N4fGYTgHY5FQE3R4Y2phk/eaEm1jS1UPuC98QuTfL
+rGuFOIBmK5euOm8uT5m9hnrouG2ZcxEdzHYfjsGDGrLzA0FLu+wtMNBKM4NhsNCa
+d+fycLg7jgxWhaLvD5DfkV7WFQlz5LUceYIwYOyhD/chAgMBAAGjLzAtMAwGA1Ud
+EwQFMAMBAf8wHQYDVR0RBBYwFIISbXRscy5odHRwYmluLmxvY2FsMA0GCSqGSIb3
+DQEBCwUAA4ICAQCNtBmoAc5tv3H38sj9qhTmabvp9RIzZYrQSEcN+A2i3a8FVYAM
+YaugZDXDcTycoWn6rcgblUDneow3NiqZ57yYZmN+e4mE3+Q1sGepV7LoRkHDUT8w
+jAJndcZ/xxJmgH6B7dImTAPsvLGR7E7gffMH+aKCdnkG9x5Vm+cuBwSEBndiHGfr
+yw5cXO6cMUq8M6zJrk2V+1BAucXW2rgLTWy6UTTGD56cgUtbStRO6muOKoElDLbW
+mSj2rNv/evakQkV8dgKVRFgh2NQKYKpXmveMaE6xtFFf/dd9OhDFjUh/ksxn94FT
+xj/wkhXCEPl+t7tENhr2tNyLbCOVcFzqoi7IyoWKxxZQfvArfj4SmahK8E/BXB/T
+4PEmn8kZAxaW7RmGcaekm8MTqGlhCJ3tVJAI2vcYRdd9ZHbXE1jr/4xj0I/Lzglo
+O8v5fd4zHyV1SuZ5AH3XbUd7ndl9yDoN2WSqK9Nd9bws3yrf+GwjJAT1InnDvLg1
+stWM8I+9FZiDFL255/+iAN0jYcGu9i4TNvC+o6qQ1p85i1OHPJZu6wtUWMgDJN46
+uwW3ZLh9sZV6OnhbQJBQaUmcgaPJUQqbXNQmpmpc0NUjET/ltFRZ2hlyvvpf7wwF
+2DLY1HRAknQ69DuT6xpYz1aKZqrlkbCWlMMvdosOg6f7+4NxdYJ/rBeS6Q==
+-----END CERTIFICATE-----
diff --git a/docs/en/latest/practices/mtls/client-ca-secret.yaml b/docs/en/latest/practices/mtls/client-ca-secret.yaml
new file mode 100644
index 0000000..fd119ef
--- /dev/null
+++ b/docs/en/latest/practices/mtls/client-ca-secret.yaml
@@ -0,0 +1,21 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+apiVersion: v1
+data:
+ cert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUY5ekNDQTkrZ0F3SUJBZ0lVRkt1ekFKWmdtL2ZzRlM2SkRyZCtsY3BWWnI4d0RRWUpLb1pJaHZjTkFRRUwKQlFBd2dad3hDekFKQmdOVkJBWVRBa05PTVJFd0R3WURWUVFJREFoYWFHVnFhV0Z1WnpFUk1BOEdBMVVFQnd3SQpTR0Z1WjNwb2IzVXhHREFXQmdOVkJBb01EMEZRU1ZOSldDMVVaWE4wTFVOQlh6RVlNQllHQTFVRUN3d1BRVkJKClUwbFlYME5CWDFKUFQxUmZNUlV3RXdZRFZRUUREQXhCVUVsVFNWZ3VVazlQVkY4eEhEQWFCZ2txaGtpRzl3MEIKQ1FFV0RYUmxjM1JBZEdWemRDNWpiMjB3SGhjTk1qRXdOVEkzTVRNek5qSTRXaGNOTWpJd05USTNNVE16TmpJNApXakNCbkRFTE1Ba0dBMVVFQmhNQ1EwNHhFVEFQQmdOVkJBZ01DRnBvWldwcFlXNW5NUkV3RHdZRFZRUUhEQWhJCllXNW5lbWh2ZFRFWU1CWUdBMVVFQ2d3UFFWQkpVMGxZTFZSbGMzUXRRMEZmTVJnd0ZnWURWUVFMREE5QlVFbFQKU1ZoZlEwRmZVazlQVkY4eEZUQVRCZ05WQkFNTURFRlFTVk5KV0M1U1QwOVVYekVjTUJvR0NTcUdTSWIzRFFFSgpBUllOZEdWemRFQjBaWE4wTG1OdmJUQ0NBaUl3RFFZSktvWklodmNOQVFFQkJRQURnZ0lQQURDQ0Fnb0NnZ0lCCkFMSlIwbFFXL0lCcVFURS9PYTBQaTRMbG1sWVVTR25xdEZOcWlaeU9GMFBqVnpOZXFvRDlKRFBpTTFRUnlDOHAKTkNkNUwvUWh0VUlNTXgwUmxESTlEa0ozQUxJV2RyUElabHdwdmVESmY0S3RXN2N6K2VhNDZBNlFRd0I2eGN5Vgp4V25xRUJraWVhN3FyRUU4TmFrWk9NamdrcWtOMi85a2xnNlh5QTVGV2Z2c3p4dHVJSHRqY3kyS3E4Yk1DMGpkCms3Q3FFWmU0Y3Q2czJ3bGNJOHQ4czlwcnZNRG04Z2NYNjZ4NEFoK0MyL1crQzNsVHBNRGdHcVJxU1B5Q1c3bmEKV2duMHRXbVRTZjFpeWJ3WU15ZGhDK3pwTTFRSkx2ZkR5cWpwMXdKaHppUjV0dFZlMlhjK3REQzI0cyt1MTZ5WgpSOTNJTzBNNGxMTmp2RUtKY01sdFh5UnpyY2p2TFhPaHczS2lyU0hOTDFLZnJCRWw3NGxiK0RWNWVVNHBJRkNqCmN1MThnbXM1RkJZczl0cEx1andwSERjMk1VK3pDdlJtU1B2VUE0eUN5b1hxb20zdWlTbzNnM3ltVzlJTThkQzgKK0JkMUdkTTZKYnBCdWt2UXliYzVUUVhvMU03NUk5aUVvUWE1dFF4QWZRL2Rmd01qT0s3c2tvZ293Qm91T3VMdgpCRUZLeTNWZDU3SVdXWlhDNHAvNzRNNk40ZkdZVGdIWTVGUUUzUjRZMnBoay9lYUVtMWpTMVVQdUM5OFF1VGZMCnJHdUZPSUJtSzVldU9tOHVUNW05aG5yb3VHMlpjeEVkekhZZmpzR0RHckx6QTBGTHUrd3RNTkJLTTROaHNOQ2EKZCtmeWNMZzdqZ3hXaGFMdkQ1RGZrVjdXRlFsejVMVWNlWUl3WU95aEQvY2hBZ01CQUFHakx6QXRNQXdHQTFVZApFd1FGTUFNQkFmOHdIUVlEVlIwUkJCWXdGSUlTYlhSc2N5NW9kSFJ3WW1sdUxteHZZMkZzTUEwR0NTcUdTSWIzCkRRRUJDd1VBQTRJQ0FRQ050Qm1vQWM1dHYzSDM4c2o5cWhUbWFidnA5Ukl6WllyUVNFY04rQTJpM2E4RlZZQU0KWWF1Z1pEWERjVHljb1duNnJjZ2JsVURuZW93M05pcVo1N3lZWm1OK2U0bUUzK1Exc0dlcFY3TG9Sa0hEVVQ4dwpqQUpuZGNaL3h4Sm1nSDZCN2RJbVRBUHN2TEdSN0U3Z2ZmTUgrYUtDZG5rRzl4NVZtK2N1QndTRUJuZGlIR2ZyCnl3NWNYTzZjTVVxOE02ekpyazJWKzFCQXVjWFcycmdMVFd5NlVUVEdENTZjZ1V0YlN0Uk82bXVPS29FbERMYlcKbVNqMnJOdi9ldmFrUWtWOGRnS1ZSRmdoMk5RS1lLcFhtdmVNYUU2eHRGRmYvZGQ5T2hERmpVaC9rc3huOTRGVAp4ai93a2hYQ0VQbCt0N3RFTmhyMnROeUxiQ09WY0Z6cW9pN0l5b1dLeHhaUWZ2QXJmajRTbWFoSzhFL0JYQi9UCjRQRW1uOGtaQXhhVzdSbUdjYWVrbThNVHFHbGhDSjN0VkpBSTJ2Y1lSZGQ5WkhiWEUxanIvNHhqMEkvTHpnbG8KTzh2NWZkNHpIeVYxU3VaNUFIM1hiVWQ3bmRsOXlEb04yV1NxSzlOZDlid3MzeXJmK0d3akpBVDFJbm5EdkxnMQpzdFdNOEkrOUZaaURGTDI1NS8raUFOMGpZY0d1OWk0VE52QytvNnFRMXA4NWkxT0hQSlp1Nnd0VVdNZ0RKTjQ2CnV3VzNaTGg5c1pWNk9uaGJRSkJRYVVtY2dhUEpVUXFiWE5RbXBtcGMwTlVqRVQvbHRGUloyaGx5dnZwZjd3d0YKMkRMWTFIUkFrblE2OUR1VDZ4cFl6MWFLWnFybGtiQ1dsTU12ZG9zT2c2ZjcrNE54ZFlKL3JCZVM2UT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
+kind: Secret
+metadata:
+ name: client-ca-secret
diff --git a/docs/en/latest/practices/mtls/mtls.yaml b/docs/en/latest/practices/mtls/mtls.yaml
new file mode 100644
index 0000000..da88150
--- /dev/null
+++ b/docs/en/latest/practices/mtls/mtls.yaml
@@ -0,0 +1,31 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+apiVersion: apisix.apache.org/v1
+kind: ApisixTls
+metadata:
+ name: sample-tls
+spec:
+ hosts:
+ - mtls.httpbin.local
+ secret:
+ name: server-secret
+ namespace: default
+ client:
+ caSecret:
+ name: client-ca-secret
+ namespace: default
+ depth: 10
diff --git a/docs/en/latest/practices/mtls/route.yaml b/docs/en/latest/practices/mtls/route.yaml
new file mode 100644
index 0000000..b50ff4f
--- /dev/null
+++ b/docs/en/latest/practices/mtls/route.yaml
@@ -0,0 +1,31 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+apiVersion: apisix.apache.org/v2alpha1
+kind: ApisixRoute
+metadata:
+ name: httpserver-route
+spec:
+ http:
+ - name: httpbin
+ match:
+ hosts:
+ - mtls.httpbin.local
+ paths:
+ - "/*"
+ backend:
+ serviceName: httpbin
+ servicePort: 80
diff --git a/docs/en/latest/practices/mtls/server-secret.yaml b/docs/en/latest/practices/mtls/server-secret.yaml
new file mode 100644
index 0000000..bfbedd8
--- /dev/null
+++ b/docs/en/latest/practices/mtls/server-secret.yaml
@@ -0,0 +1,23 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+apiVersion: v1
+data:
+ cert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUYvVENDQStXZ0F3SUJBZ0lVQmJVUDdHazBXQWIvSmhZWWNCQmdaRWdtaGJFd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2dad3hDekFKQmdOVkJBWVRBa05PTVJFd0R3WURWUVFJREFoYWFHVnFhV0Z1WnpFUk1BOEdBMVVFQnd3SQpTR0Z1WjNwb2IzVXhHREFXQmdOVkJBb01EMEZRU1ZOSldDMVVaWE4wTFVOQlh6RVlNQllHQTFVRUN3d1BRVkJKClUwbFlYME5CWDFKUFQxUmZNUlV3RXdZRFZRUUREQXhCVUVsVFNWZ3VVazlQVkY4eEhEQWFCZ2txaGtpRzl3MEIKQ1FFV0RYUmxjM1JBZEdWemRDNWpiMjB3SGhjTk1qRXdOVEkzTVRNek5qSTVXaGNOTWpJd05USTNNVE16TmpJNQpXakNCcFRFTE1Ba0dBMVVFQmhNQ1EwNHhFVEFQQmdOVkJBZ01DRnBvWldwcFlXNW5NUkV3RHdZRFZRUUhEQWhJCllXNW5lbWh2ZFRFY01Cb0dBMVVFQ2d3VFFWQkpVMGxZTFZSbGMzUXRVMlZ5ZG1WeVh6RVhNQlVHQTFVRUN3d08KUVZCSlUwbFlYMU5GVWxaRlVsOHhHekFaQmdOVkJBTU1FbTEwYkhNdWFIUjBjR0pwYmk1c2IyTmhiREVjTUJvRwpDU3FHU0liM0RRRUpBUllOZEdWemRFQjBaWE4wTG1OdmJUQ0NBaUl3RFFZSktvWklodmNOQVFFQkJRQURnZ0lQCkFEQ0NBZ29DZ2dJQkFNWlJQRzhnVXJPR000YXduVjZEOERzMFhiNmpWYmlHa3grMVlzdlB4NW9JRTRBc3dKMGwKeTZ6cWhCRm5wUW96Rkc2M0tmc0NBNlUzNi9EdHkzckliSnpzYk83WWFPTUpJdG9pUWdxZHFGMm5ybVBwbXBDUQp1TEdLYVZ2cmlSQ0Q1NU5FbUZRUHNobFJmY1U1L0VFcmVOS2JSdmUzekVLSFJwQ0RCWjJNeXZycHQzQ0NWeTZECk1iTGxsYmpVdmFlZHJuUXhsbUk1ZDd4M1VDZTRFdW5xOHZuN2MwcDRmckExbjhUeGJYME00WXI5ZzNZRUVxQ3YKUTMvOWpVNGhJNUN2dWpDcCt1NzlFYXZKWmZzYUV2M1JZZ0hrb0VoN3ErT0VrVWFqV1hLajRXeW5penJhV3NIdgorTHZLOXBmSTMwMHAxSFNLSzRGcW9udlc3OWFuUk5iSys4QnFWNFd0NWFCZUZVL3JXMmpIdEp4Y2wxT0xScnJoCndmdENQNVc3dlNqdkplczV3UERaakRFeXY4V1AxQWE2eVdlR0hIdEl3ckFIUHI3NTU2Ri9KQVFTNklQQlFRNVUKWDQ1REQyYU5YTUU5eFpLZEJ0eU1vdkl0alptMzFVVXN2b0YrWXRwQU9tYkVrWDRsTXpuVU8zWFpKak01SFdTcQpXWXl6bUZzdytwSkV3aFhSbzRHZlNmQ0hmaVpRNmltVExKN09zWnpvOWJ2bXh5Zkkwa1ZMZTNoM2lDZStxWWVUCmY1QUpVNnY1dnYzdGhDdGZnYnhZUDJQOGIrME1JcmZyMDVlNmRDRFhiSXJpMXorbnByeldZbXlDclo2SDRoVmsKRHpNa3RGVWxrcWVudm5zSjJpT1YyQVp3MEhsazJid2U0elN1bXpxb0lwOFlrL2t4YmZ4aFFxcjVBZ01CQUFHagpMREFxTUFrR0ExVWRFd1FDTUFBd0hRWURWUjBSQkJZd0ZJSVNiWFJzY3k1b2RIUndZbWx1TG14dlkyRnNNQTBHCkNTcUdTSWIzRFFFQkN3VUFBNElDQVFDRERmRVRDRXBXQi9LUlFabzJKRjhuNE5FRFRlcmFRODVNM0g1bHVKSHAKTmRKTzRvWXEzbjhCMTQ5ZXA0RmNFWWRPMjBwVitUTWVNTldYTWZob1JJcEd4OTVKckx1TGc2cW53NmVOZEVybgpZdXBITUMyT0VvRVdWY21JMDUyTERKY1h1S3NUWFF2VTRPZUVMMmRYNE90TkorbVJPREx5aDQwY2c3ZEEzd3J5CmtHTGlwclJsTFF0aVg4cFNERzMwcVBaZXhMMUxjRnpCUWFqcmlHMDVRVXJKVzZSdmJxMUpUSWx5cDdFMVQ4NmYKWGxqcTBIZHpxeHkrRmtsWWNBVzVaQXhna1FsTW1WZFRsdkRYbEQvaFFMRVFJSEdIaVc2T01McDhXcm5KUDZiMApEMkhxV21Pd3VFenFTZ1hTSzBOODlycGlXUDFGS0NweWlLVmNzYXdETmZPcGVQVnV0aG9tbVZFYzJQeGFjeUhmClVDQzlWME1TMFp6UTYzVG56MlRqYThDNi9rTXlWWDIyNktRS2hjb0R4RG9TMG1Rckk5Ni9WWGNnbHdQNWhNakYKam90aDFUMXFSVnU2K05RbXZGUGFOamJ6V0orajFSOTlibllHaWhQZUxkcURTVXhOb3NWM1VMRzhUNGFONitmOApoQXBpcWcyZGtMSlFyOHpXZjZ2V1hNbFJFZFBFb3ZiMkY3UDBMZm4wVmVPU1JYRFVJZHFjb1JIT05pOGJXTVJzCmZqUHRHVzAwVHY4SmcyMWM5dmM4WmgvdDF3M3drWFFocVlpQk10NWNZZTZXdWVJbFhkakY3aWtTUldBSFR3bHcKQmZ6di92TWZ0TG5ieVNQb3ZDelExUEY1NUQwMUVXUmswbzZQUndVRExmelRRb1YrYkRLeDgyTHhLdFpCdFFFWAp1dz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
+ key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlKS0FJQkFBS0NBZ0VBeGxFOGJ5QlNzNFl6aHJDZFhvUHdPelJkdnFOVnVJYVRIN1ZpeTgvSG1nZ1RnQ3pBCm5TWExyT3FFRVdlbENqTVVicmNwK3dJRHBUZnI4TzNMZXNoc25PeHM3dGhvNHdraTJpSkNDcDJvWGFldVkrbWEKa0pDNHNZcHBXK3VKRUlQbmswU1lWQSt5R1ZGOXhUbjhRU3Q0MHB0Rzk3Zk1Rb2RHa0lNRm5ZeksrdW0zY0lKWApMb014c3VXVnVOUzlwNTJ1ZERHV1lqbDN2SGRRSjdnUzZlcnkrZnR6U25oK3NEV2Z4UEZ0ZlF6aGl2MkRkZ1FTCm9LOURmLzJOVGlFamtLKzZNS242N3YwUnE4bGwreG9TL2RGaUFlU2dTSHVyNDRTUlJxTlpjcVBoYktlTE90cGEKd2UvNHU4cjJsOGpmVFNuVWRJb3JnV3FpZTlidjFxZEUxc3I3d0dwWGhhM2xvRjRWVCt0YmFNZTBuRnlYVTR0Rwp1dUhCKzBJL2xidTlLTzhsNnpuQThObU1NVEsveFkvVUJyckpaNFljZTBqQ3NBYyt2dm5ub1g4a0JCTG9nOEZCCkRsUmZqa01QWm8xY3dUM0ZrcDBHM0l5aThpMk5tYmZWUlN5K2dYNWkya0E2WnNTUmZpVXpPZFE3ZGRrbU16a2QKWktwWmpMT1lXekQ2a2tUQ0ZkR2pnWjlKOElkK0psRHFLWk1zbnM2eG5PajF1K2JISjhqU1JVdDdlSGVJSjc2cApoNU4va0FsVHEvbSsvZTJFSzErQnZGZy9ZL3h2N1F3aXQrdlRsN3AwSU5kc2l1TFhQNmVtdk5aaWJJS3Rub2ZpCkZXUVBNeVMwVlNXU3A2ZStld25hSTVYWUJuRFFlV1RadkI3ak5LNmJPcWdpbnhpVCtURnQvR0ZDcXZrQ0F3RUEKQVFLQ0FnQlA2dWk1dDRMY1NaWjJEckk4SmxzbTRLRnVjNC9WdnBXSFQ2Y3lqdGJXNGE1S0ZyN0FGVDBRdjZqZApBckZsZk5RZEViN2ZJaDZwOC9FbXRBMHR1NXJaV2dWRDh2M0JrQ3IxVUp6Z2Zrd2RBYmVyRjdacno0WStOWkxqCnNmVVlMSytqang3N3NSK0tTR2F3bGY5cm04TWl5K1E3YTF2cTYyeXFTOEoxalFrM04vdnVZUGdWREZWNHpFQWIKcmMrSHZtbFE5Ykt1Zm80YjZ0RG9VS3QrakduQ0IyeWNkQlpKbURKOFFQWm9VRXFMb2tIWnl5WmVqb0piRDZoago5Y0xKU2FkMGVPdGdaNmM1WFAyMXhQb21RcnlHR3NYa3I4SEMrK2MzV2hodnRFN2haRnNkS21Vc2hqSHNLNHhYCittRFNUYXNLRTZ3WWlRcFZjWFpSUURMamhBVVMvWXJvMmY0WkZxUW1BVWtzekxDS3FsMEJOWFlzUkdaMDNHdlgKS1krS2ROME1VQkpTVGVKdXV0OStFUkZ4dEJFYThtN1dKam5xTGNqRE04N1BDWWpla3ZnbitCQTUxVTZoTTRkRwpGSmtTZDhUeHh1Z1crZit1em5GbmJ2QkVRNmZvakRMaFhLbGlScnJiV09aUy9scDdObitwTTRUbks1K3F1UUIwCnNTWThMTkQ5MWtrMUhFV2U0RW9jTWhVTTZDcFgxU3QxenJRYkxxNW5veiswMzZuL1ZUL3RZbHJyOUdMaFJNSU4KS0VXbHllUE5TY2VqT2ZYMk8zaWkwSk9JR1NJUWFQd29JYTNycnM1TXBOMEx2dlNOdW9LbDFVcXhYWXhXMy83cgpoVHdRblVMVlRwRHg2QjZYMlp2d2JmN1c4djlOS240Qmp2cXJTMVVJMjA5cVJoL3ZJUUtDQVFFQTZqYjlpc0dTClM1dWEwbjkybm1KemRaUElieTNaZEVhSnV3cVlZUVdDTFEwWmp5MFlZVjBlQW1XWEtxKzFWdGVOYmR4K0NYZWEKdzRIZUhxc2NuS3hsVEZ6OXNiS0YzNEJNaUs1Uk5UWHpIK09za3NJWHB0N3dISnlOczdvWDRNUENlY3pnRnhvQwpxd1lLOVNJYVpZVjc2OHkyVExSaVMvVFdORXAram1Bbkd3MTJValROcTNXTEtMRzd2aEc3U0kzcmgwTHRsR3lOCkV6R0dxMlQ3blBsM29wRXNlMGp0bWJwSmhMN1JYSnVnVHNIbU5Db0VCQitKZk5YR1FFbHdQV0czVGdOQkdIQm0KNTgweHViL0pFR3FkZkptTFp0dEQ3UGFhK2NuRlVYU1RIR21pQy9yOUU3anVNaWUybm9OaVovSmhxckpvM1Z2eApzTy9tUml1S2lBeWtiUUtDQVFFQTJNTjQ2UGpMQWJ1WW42bUFUaVI0eVNsajR0ckV2OVJXa29DbzJwK1N0V0pYClNZcWRLZk9ySU53M3FBeThnWTlDNGoyeUxBcXlQYVFybGhDZW9HLzdHSm4xSk5KdEIyNG1nZnFoQnFpV2krMHEKcHBXRDg1bnViU1JuT2dYdjNJWTJHOVgrK1JSTjE4WS9yaEJGVTZJREpVcHlaNDJHNC9DR2tTL0I1NlkyVXdIUQphc3VETGtybEpmS0xoMm9tZU1SdE9Ia0hJV29NbFFjbmQ2aVNJcTdwams3TDhCSDNhQWlSMXB6Y2g2dGNzYThrCndrd1BGbWZHb2ZkWEU1aGQvU3dXM3REN1g1OHJLbjl5RWJaVElzNjR5K0JQSm9iKys0eFVDamFLNXlQSUNDckYKOE1PUEI4NThUQW03Y245VEZnS1pwdjlkbVVLdzFoVktMOVBLUVgxUlBRS0NBUUVBNHpsNFh3Nk8vTlU0cmZGRgpSa0dqWERXRXBnQW9VSHRDa2Zpa2ZyUVdaOWltckZZR3FpYnB2MCtLQ2JxdnhsR1cvemVEKzNGUzcwdm1ENERZCllGT01ienBrVWVvdG9QamF4MXUrbzAzMDBrSlNvWXExNFltMkR6dis2WmVvSk1JbXdYMzNCZEtSTmhURnVxNWMKUjVQcDlva0RiNFV0UEIyTFZ1M1N2QlFpdkVjaVBIekg4QWs0ZWNGOHI5aUtCc2pROE1nSXNBOWtDblBwQUEwWApZbUpRSTZLT01nazlvZit0NWFBdWc1YmtQcVEwenZUWU1wdmFDZ2RucitUUGhHMXhwYmpZaFhvL0M3SHlCUkJBClk3SGJtZzlvdytBRGxUaG1mK0cxa2VIeit3T3NWODBuaStQRkMxbWwvVURmenBMREdCVEFVY2txd1FydEw3UjgKVUtOYlBRS0NBUUJFK1g1aDg3ajFaakpjcTkwT0FJRUcwY3JkQnV3UWRvck50MjhEa2o5bXhGSXVMcE53SS85UwpSNERXVXFjeE90cjNqdFpCT1c0YU8wRTdVVEtJcnRsaHJLdmErYktENk1NTUhTcGNLZzB0blZ3ekFlU3BBVlJqCkduQldnRWtoRFB2dXc1dU11cTlDZCswUGdGSHZHT0NUWHlza1ZGNlY3WldFWVlQOEtHR2s3RERicXNLbFdtT3MKUFkrMG1VeUFwVkJ6NWQ4ay9NL2dKQlNrK05qM2ZGMEpVWDJIZU5BWEpKTHpqWnFHK1RwWHQvbWtjZnRqRDhhZgpCMHVJQ3JYdHQ3ZlhVdnlLSXVYamNnWmtLSFl2MzBQaWJCQURuSFZLcWc2YjZWc3R6YTc3R2xFK0daeEx5YUszCnQya1VOL3ZDUnpXSmREemVaZUJMWHg3cU5TUm96bTJwQW9JQkFHeGVxaWQzczM2UVkzeHJ1ZlE1VzNNY3RCWHkKRHRmZkgxbHREdEFhSWhFa0ovaWFaTks1RUhWY2FXQXBpTDhxVzdFak9WT0FvZ2xhSlh0VDcvcXk3QVNkNDJOSAozcTUwZ1R3TUY0dzBja0o1VlRnWXFGeEFvU3grdGxBaGRiQndrMGtMVWl4L3RDSzJFdURUVGZGd05obVZKbEJ1CjZVZkJzLzlscGJvV1FSMWdzZU52d3JVVUIyN2gyNmR3SkpUZVFXQ1JZa0EvSWc0dHRjLzc5cUVuOHhWNFA0VGsKdzE3NFJTUW9OTWMrb2RIeG45NW14dFlkWVZFNVBLa3pncmZ4cXltTGE1WTBMTVBDcEtPcTRYQjBwYVpQdHJPdAprMVhib2dTNkVZeUVkYmtURGRYZFVFTnZEclU3aHpKWFNWeEpZQURpcXI0NERHZldtNmhLMGJxOVpQYz0KLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0K
+kind: Secret
+metadata:
+ name: server-secret
diff --git a/docs/en/latest/practices/mtls/server.key b/docs/en/latest/practices/mtls/server.key
new file mode 100644
index 0000000..6c0e3d4
--- /dev/null
+++ b/docs/en/latest/practices/mtls/server.key
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----
diff --git a/docs/en/latest/practices/mtls/server.pem b/docs/en/latest/practices/mtls/server.pem
new file mode 100644
index 0000000..b253f81
--- /dev/null
+++ b/docs/en/latest/practices/mtls/server.pem
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+MIIF/TCCA+WgAwIBAgIUBbUP7Gk0WAb/JhYYcBBgZEgmhbEwDQYJKoZIhvcNAQEL
+BQAwgZwxCzAJBgNVBAYTAkNOMREwDwYDVQQIDAhaaGVqaWFuZzERMA8GA1UEBwwI
+SGFuZ3pob3UxGDAWBgNVBAoMD0FQSVNJWC1UZXN0LUNBXzEYMBYGA1UECwwPQVBJ
+U0lYX0NBX1JPT1RfMRUwEwYDVQQDDAxBUElTSVguUk9PVF8xHDAaBgkqhkiG9w0B
+CQEWDXRlc3RAdGVzdC5jb20wHhcNMjEwNTI3MTMzNjI5WhcNMjIwNTI3MTMzNjI5
+WjCBpTELMAkGA1UEBhMCQ04xETAPBgNVBAgMCFpoZWppYW5nMREwDwYDVQQHDAhI
+YW5nemhvdTEcMBoGA1UECgwTQVBJU0lYLVRlc3QtU2VydmVyXzEXMBUGA1UECwwO
+QVBJU0lYX1NFUlZFUl8xGzAZBgNVBAMMEm10bHMuaHR0cGJpbi5sb2NhbDEcMBoG
+CSqGSIb3DQEJARYNdGVzdEB0ZXN0LmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIP
+ADCCAgoCggIBAMZRPG8gUrOGM4awnV6D8Ds0Xb6jVbiGkx+1YsvPx5oIE4AswJ0l
+y6zqhBFnpQozFG63KfsCA6U36/Dty3rIbJzsbO7YaOMJItoiQgqdqF2nrmPpmpCQ
+uLGKaVvriRCD55NEmFQPshlRfcU5/EEreNKbRve3zEKHRpCDBZ2Myvrpt3CCVy6D
+MbLllbjUvaedrnQxlmI5d7x3UCe4Eunq8vn7c0p4frA1n8TxbX0M4Yr9g3YEEqCv
+Q3/9jU4hI5CvujCp+u79EavJZfsaEv3RYgHkoEh7q+OEkUajWXKj4WynizraWsHv
++LvK9pfI300p1HSKK4FqonvW79anRNbK+8BqV4Wt5aBeFU/rW2jHtJxcl1OLRrrh
+wftCP5W7vSjvJes5wPDZjDEyv8WP1Aa6yWeGHHtIwrAHPr7556F/JAQS6IPBQQ5U
+X45DD2aNXME9xZKdBtyMovItjZm31UUsvoF+YtpAOmbEkX4lMznUO3XZJjM5HWSq
+WYyzmFsw+pJEwhXRo4GfSfCHfiZQ6imTLJ7OsZzo9bvmxyfI0kVLe3h3iCe+qYeT
+f5AJU6v5vv3thCtfgbxYP2P8b+0MIrfr05e6dCDXbIri1z+nprzWYmyCrZ6H4hVk
+DzMktFUlkqenvnsJ2iOV2AZw0Hlk2bwe4zSumzqoIp8Yk/kxbfxhQqr5AgMBAAGj
+LDAqMAkGA1UdEwQCMAAwHQYDVR0RBBYwFIISbXRscy5odHRwYmluLmxvY2FsMA0G
+CSqGSIb3DQEBCwUAA4ICAQCDDfETCEpWB/KRQZo2JF8n4NEDTeraQ85M3H5luJHp
+NdJO4oYq3n8B149ep4FcEYdO20pV+TMeMNWXMfhoRIpGx95JrLuLg6qnw6eNdErn
+YupHMC2OEoEWVcmI052LDJcXuKsTXQvU4OeEL2dX4OtNJ+mRODLyh40cg7dA3wry
+kGLiprRlLQtiX8pSDG30qPZexL1LcFzBQajriG05QUrJW6Rvbq1JTIlyp7E1T86f
+Xljq0Hdzqxy+FklYcAW5ZAxgkQlMmVdTlvDXlD/hQLEQIHGHiW6OMLp8WrnJP6b0
+D2HqWmOwuEzqSgXSK0N89rpiWP1FKCpyiKVcsawDNfOpePVuthommVEc2PxacyHf
+UCC9V0MS0ZzQ63Tnz2Tja8C6/kMyVX226KQKhcoDxDoS0mQrI96/VXcglwP5hMjF
+joth1T1qRVu6+NQmvFPaNjbzWJ+j1R99bnYGihPeLdqDSUxNosV3ULG8T4aN6+f8
+hApiqg2dkLJQr8zWf6vWXMlREdPEovb2F7P0Lfn0VeOSRXDUIdqcoRHONi8bWMRs
+fjPtGW00Tv8Jg21c9vc8Zh/t1w3wkXQhqYiBMt5cYe6WueIlXdjF7ikSRWAHTwlw
+Bfzv/vMftLnbySPovCzQ1PF55D01EWRk0o6PRwUDLfzTQoV+bDKx82LxKtZBtQEX
+uw==
+-----END CERTIFICATE-----
diff --git a/docs/en/latest/practices/mtls/tls.yaml b/docs/en/latest/practices/mtls/tls.yaml
new file mode 100644
index 0000000..bda748a
--- /dev/null
+++ b/docs/en/latest/practices/mtls/tls.yaml
@@ -0,0 +1,26 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+apiVersion: apisix.apache.org/v1
+kind: ApisixTls
+metadata:
+ name: sample-tls
+spec:
+ hosts:
+ - mtls.httpbin.local
+ secret:
+ name: server-secret
+ namespace: default
diff --git a/docs/en/latest/practices/mtls/user.key b/docs/en/latest/practices/mtls/user.key
new file mode 100644
index 0000000..5e0feba
--- /dev/null
+++ b/docs/en/latest/practices/mtls/user.key
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----
diff --git a/docs/en/latest/practices/mtls/user.pem b/docs/en/latest/practices/mtls/user.pem
new file mode 100644
index 0000000..a6b3746
--- /dev/null
+++ b/docs/en/latest/practices/mtls/user.pem
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+MIIGDDCCA/SgAwIBAgIUBbUP7Gk0WAb/JhYYcBBgZEgmhbIwDQYJKoZIhvcNAQEL
+BQAwgZwxCzAJBgNVBAYTAkNOMREwDwYDVQQIDAhaaGVqaWFuZzERMA8GA1UEBwwI
+SGFuZ3pob3UxGDAWBgNVBAoMD0FQSVNJWC1UZXN0LUNBXzEYMBYGA1UECwwPQVBJ
+U0lYX0NBX1JPT1RfMRUwEwYDVQQDDAxBUElTSVguUk9PVF8xHDAaBgkqhkiG9w0B
+CQEWDXRlc3RAdGVzdC5jb20wHhcNMjEwNTI3MTMzNjMxWhcNMjIwNTI3MTMzNjMx
+WjCBtDELMAkGA1UEBhMCQ04xETAPBgNVBAgMCFpoZWppYW5nMREwDwYDVQQHDAhI
+YW5nemhvdTEfMB0GA1UECgwWQVBJU0lYLVRlc3QtUm9vdC1Vc2VyXzEaMBgGA1UE
+CwwRQVBJU0lYX1JPT1RfVVNFUl8xJDAiBgNVBAMMG0JpZ01pbmdfIDY1NDMxMTEx
+MTExMTExMTExMTEcMBoGCSqGSIb3DQEJARYNdGVzdEB0ZXN0LmNvbTCCAiIwDQYJ
+KoZIhvcNAQEBBQADggIPADCCAgoCggIBAL41i+W8fgqtKButnkOa0qGApPJ7HSUI
+gxBt6Tb7mR8eka8vno8hIE42hwMXYoIiO1FgM9M9fPN8vzTnWr2iTAXbw/qXdn14
+RT01JrGzj1OU3M9U0RMmDWVs8VOY3ncdcdPoqctMNr2K+6wlN6EpG1DQDUEvB0Yy
+qZpFWpuZP0akzjFqeJNSo3b8hP4J1nsWBxKt6TRInC60jT1mhKCjTB1IsXVC+5SY
+f0FhMN2ZQPgCyIIq+j5S7tHta4Qs+UDNY0whIMBkY3ZAGPzUBFExwrX7bGNbeJYz
+DahNAKiEj5dlieRvzTn5UisSv5EzDUONLSluEnCU1Ghd/dfZtZwlMBU6OIflwm6W
+X7HePD0G8XEDe5qbU/tg/62j+A1hMS2gSlibNisyvNwMNz6Imc3CnIFUL2mTsgl1
+bCBLfOXDfrjte5Txnt26ayN0+WhmVSts0+ln6vnGtgVGqH0bmp95yMRYPej+6ARE
+MtfvrNA7iZORypspv2NVyV11b6Jx3o/pJOAq9dmAsKvJ6hK0IVf/55t3ZtzRa/fY
+w0f78KbWuEc7qmx2tBxnEfppb5iB/RFmI2bDaAEA0lDQgAqmiAu1utetfqsCwA1m
+3VXGp8Vp+lexA/M3Bkb5BHa6C11QDHvGjUVwduOymLAHKumvdgf7TJFnlhpg6jYN
+unO58bRSTI6LAgMBAAGjLDAqMAkGA1UdEwQCMAAwHQYDVR0RBBYwFIISbXRscy5o
+dHRwYmluLmxvY2FsMA0GCSqGSIb3DQEBCwUAA4ICAQCoTBvw11aKah2cuB4XplUB
+nmkrWhfLNFJLVrU9jIISP9Wkl3s3PcM+aEWygb/1roqbMqNOgrzDVgGRRFCiS6qi
+himUuTJhIBI6TF1PE+XW3gTFWBXkAZ7MzpbS8oP1PehlY3XXKNZgxZi3XaDI8Hfw
+5MWBGNbk8tegn8bvYQUz2VxmCo6zufCkj4ADjw2zhiyKBKuHTzg48w66Wn4jLhlK
+p91HHrK0lEOIJ4pFmBUpBsSJMlBMbfrzBF87xQhpDO3ScFfCWUatShmXsPMJU0F0
+DEuTnaHUefUf/F9wUGNcA4yQ4pH8SxVpRHmrWE8U4uSXpz1bx4ChZurJ9mPzrj9h
+U9c/d9F5WndZNPcR1R8Tbzhk/R8GImVR3Lt59cW+SN/+4JVFy+Hye2yslGFn2CAJ
+ofNxjLb9OE6+EE3SWW0B5CZSWBS49gtdTW0ApOjIRJU2zipxcjnNf00GFoIoCxjk
+Z4eBQz9WVUM9KSrJIQSLZQd35tZAOp0BuwWho0+w8lXUchSqT7oRA7+szZldWF0j
+HKPIMJ0iVWmXuZjsS8q8NBIt4DuBcqpevlol5KRXv6tJy4IBVAVEIBdeXotvdxKE
+bncvZ6xo9A/waUU7tEyzv34usxefrWxtSlOA1G0Jj4nb5gKPHjn0XIr9WI2RpovT
+/XpB6QES1zoBQya3QjnDbQ==
+-----END CERTIFICATE-----
diff --git a/pkg/apisix/ssl.go b/pkg/apisix/ssl.go
index 16a9f7c..257117b 100644
--- a/pkg/apisix/ssl.go
+++ b/pkg/apisix/ssl.go
@@ -141,13 +141,7 @@
if err := s.cluster.HasSynced(ctx); err != nil {
return nil, err
}
- data, err := json.Marshal(v1.Ssl{
- ID: obj.ID,
- Snis: obj.Snis,
- Cert: obj.Cert,
- Key: obj.Key,
- Status: obj.Status,
- })
+ data, err := json.Marshal(obj)
if err != nil {
return nil, err
}
diff --git a/pkg/ingress/apisix_tls.go b/pkg/ingress/apisix_tls.go
index 643d37b..2ba036d 100644
--- a/pkg/ingress/apisix_tls.go
+++ b/pkg/ingress/apisix_tls.go
@@ -123,13 +123,19 @@
c.controller.recordStatus(tls, _resourceSyncAborted, err, metav1.ConditionFalse)
return err
}
- log.Debug("got SSL object from ApisixTls",
+ log.Debugw("got SSL object from ApisixTls",
zap.Any("ssl", ssl),
zap.Any("ApisixTls", tls),
)
secretKey := tls.Spec.Secret.Namespace + "_" + tls.Spec.Secret.Name
- c.syncSecretSSL(secretKey, ssl, ev.Type)
+ c.syncSecretSSL(secretKey, key, ssl, ev.Type)
+ if tls.Spec.Client != nil {
+ caSecretKey := tls.Spec.Client.CASecret.Namespace + "_" + tls.Spec.Client.CASecret.Name
+ if caSecretKey != secretKey {
+ c.syncSecretSSL(caSecretKey, key, ssl, ev.Type)
+ }
+ }
if err := c.controller.syncSSL(ctx, ssl, ev.Type); err != nil {
log.Errorw("failed to sync SSL to APISIX",
@@ -146,21 +152,21 @@
return err
}
-func (c *apisixTlsController) syncSecretSSL(key string, ssl *v1.Ssl, event types.EventType) {
- if ssls, ok := c.controller.secretSSLMap.Load(key); ok {
+func (c *apisixTlsController) syncSecretSSL(secretKey string, apisixTlsKey string, ssl *v1.Ssl, event types.EventType) {
+ if ssls, ok := c.controller.secretSSLMap.Load(secretKey); ok {
sslMap := ssls.(*sync.Map)
switch event {
case types.EventDelete:
- sslMap.Delete(ssl.ID)
- c.controller.secretSSLMap.Store(key, sslMap)
+ sslMap.Delete(apisixTlsKey)
+ c.controller.secretSSLMap.Store(secretKey, sslMap)
default:
- sslMap.Store(ssl.ID, ssl)
- c.controller.secretSSLMap.Store(key, sslMap)
+ sslMap.Store(apisixTlsKey, ssl)
+ c.controller.secretSSLMap.Store(secretKey, sslMap)
}
} else if event != types.EventDelete {
sslMap := new(sync.Map)
- sslMap.Store(ssl.ID, ssl)
- c.controller.secretSSLMap.Store(key, sslMap)
+ sslMap.Store(apisixTlsKey, ssl)
+ c.controller.secretSSLMap.Store(secretKey, sslMap)
}
}
diff --git a/pkg/ingress/controller.go b/pkg/ingress/controller.go
index 4568155..04ecfe1 100644
--- a/pkg/ingress/controller.go
+++ b/pkg/ingress/controller.go
@@ -246,6 +246,11 @@
}
}
+// recorderEvent recorder events for resources
+func (c *Controller) recorderEventS(object runtime.Object, eventtype, reason string, msg string) {
+ c.recorder.Event(object, eventtype, reason, msg)
+}
+
func (c *Controller) goAttach(handler func()) {
c.wg.Add(1)
go func() {
diff --git a/pkg/ingress/secret.go b/pkg/ingress/secret.go
index 8b3aec2..2d8e763 100644
--- a/pkg/ingress/secret.go
+++ b/pkg/ingress/secret.go
@@ -17,12 +17,14 @@
import (
"context"
+ "fmt"
"sync"
"time"
"go.uber.org/zap"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
@@ -130,21 +132,62 @@
// This secret is not concerned.
return nil
}
- cert, ok := sec.Data["cert"]
- if !ok {
- return translation.ErrEmptyCert
- }
- pkey, ok := sec.Data["key"]
- if !ok {
- return translation.ErrEmptyPrivKey
- }
sslMap := ssls.(*sync.Map)
- sslMap.Range(func(_, v interface{}) bool {
+ sslMap.Range(func(k, v interface{}) bool {
ssl := v.(*apisixv1.Ssl)
- // sync ssl
- ssl.Cert = string(cert)
- ssl.Key = string(pkey)
-
+ tlsMetaKey := k.(string)
+ tlsNamespace, tlsName, err := cache.SplitMetaNamespaceKey(tlsMetaKey)
+ if err != nil {
+ log.Errorf("invalid cached ApisixTls key: %s", tlsMetaKey)
+ return true
+ }
+ tls, err := c.controller.apisixTlsLister.ApisixTlses(tlsNamespace).Get(tlsName)
+ if err != nil {
+ log.Warnw("secret related ApisixTls resource not found, skip",
+ zap.String("ApisixTls", tlsMetaKey),
+ )
+ return true
+ }
+ if tls.Spec.Secret.Namespace == sec.Namespace && tls.Spec.Secret.Name == sec.Name {
+ cert, ok := sec.Data["cert"]
+ if !ok {
+ log.Warnw("secret required by ApisixTls invalid",
+ zap.String("ApisixTls", tlsMetaKey),
+ zap.Error(translation.ErrEmptyCert),
+ )
+ return true
+ }
+ pkey, ok := sec.Data["key"]
+ if !ok {
+ log.Warnw("secret required by ApisixTls invalid",
+ zap.String("ApisixTls", tlsMetaKey),
+ zap.Error(translation.ErrEmptyPrivKey),
+ )
+ return true
+ }
+ // sync ssl
+ ssl.Cert = string(cert)
+ ssl.Key = string(pkey)
+ } else if tls.Spec.Client != nil &&
+ tls.Spec.Client.CASecret.Namespace == sec.Namespace && tls.Spec.Client.CASecret.Name == sec.Name {
+ ca, ok := sec.Data["cert"]
+ if !ok {
+ log.Warnw("secret required by ApisixTls invalid",
+ zap.String("resource", tlsMetaKey),
+ zap.Error(translation.ErrEmptyCert),
+ )
+ return true
+ }
+ ssl.Client = &apisixv1.MutualTLSClientConfig{
+ CA: string(ca),
+ }
+ } else {
+ log.Warnw("stale secret cache, ApisixTls doesn't requires target secret",
+ zap.String("ApisixTls", tlsMetaKey),
+ zap.String("secret", key),
+ )
+ return true
+ }
// Use another goroutine to send requests, to avoid
// long time lock occupying.
go func(ssl *apisixv1.Ssl) {
@@ -155,6 +198,13 @@
zap.Any("ssl", ssl),
zap.Any("secret", sec),
)
+ c.controller.recorderEventS(tls, corev1.EventTypeWarning, _resourceSyncAborted,
+ fmt.Sprintf("sync from secret %s changes failed, error: %s", key, err.Error()))
+ c.controller.recordStatus(tls, _resourceSyncAborted, err, metav1.ConditionFalse)
+ } else {
+ c.controller.recorderEventS(tls, corev1.EventTypeNormal, _resourceSynced,
+ fmt.Sprintf("sync from secret %s changes", key))
+ c.controller.recordStatus(tls, _resourceSynced, nil, metav1.ConditionTrue)
}
}(ssl)
return true
diff --git a/pkg/kube/apisix/apis/config/v1/types.go b/pkg/kube/apisix/apis/config/v1/types.go
index 72a4e2c..a8b9181 100644
--- a/pkg/kube/apisix/apis/config/v1/types.go
+++ b/pkg/kube/apisix/apis/config/v1/types.go
@@ -29,6 +29,8 @@
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// ApisixRoute is used to define the route rules and upstreams for Apache APISIX.
// The definition closes the Kubernetes Ingress resource.
+// +kubebuilder:resource:shortName=ar
+// +kubebuilder:pruning:PreserveUnknownFields
type ApisixRoute struct {
metav1.TypeMeta `json:",inline" yaml:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
@@ -268,30 +270,58 @@
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+// +kubebuilder:resource:shortName=atls
// +kubebuilder:subresource:status
// ApisixTls defines SSL resource in APISIX.
type ApisixTls struct {
metav1.TypeMeta `json:",inline" yaml:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
- Spec *ApisixTlsSpec `json:"spec,omitempty" yaml:"spec,omitempty"`
- Status v2alpha1.ApisixStatus `json:"status,omitempty" yaml:"status,omitempty"`
+ Spec *ApisixTlsSpec `json:"spec,omitempty" yaml:"spec,omitempty"`
+ // +optional
+ Status v2alpha1.ApisixStatus `json:"status,omitempty" yaml:"status,omitempty"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+// +kubebuilder:printcolumn:name="SNIs",type=string,JSONPath=`.spec.hosts`
+// +kubebuilder:printcolumn:name="Secret Name",type=string,JSONPath=`.spec.secret.name`
+// +kubebuilder:printcolumn:name="Secret Namespace",type=string,JSONPath=`.spec.secret.namespace`
+// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
+// +kubebuilder:printcolumn:name="Client CA Secret Name",type=string,JSONPath=`.spec.client.ca.name`
+// +kubebuilder:printcolumn:name="Client CA Secret Namespace",type=string,JSONPath=`.spec.client.ca.namespace`
type ApisixTlsList struct {
metav1.TypeMeta `json:",inline" yaml:",inline"`
metav1.ListMeta `json:"metadata" yaml:"metadata"`
Items []ApisixTls `json:"items,omitempty" yaml:"items,omitempty"`
}
+// +kubebuilder:validation:Pattern="^\\*?[0-9a-zA-Z-.]+$"
+type HostType string
+
// ApisixTlsSpec is the specification of ApisixSSL.
type ApisixTlsSpec struct {
- Hosts []string `json:"hosts,omitempty" yaml:"hosts,omitempty"`
- Secret ApisixSecret `json:"secret,omitempty" yaml:"secret,omitempty"`
+ // +required
+ // +kubebuilder:validation:Required
+ // +kubebuilder:validation:MinItems=1
+ Hosts []HostType `json:"hosts" yaml:"hosts,omitempty"`
+ // +required
+ // +kubebuilder:validation:Required
+ Secret ApisixSecret `json:"secret" yaml:"secret"`
+ // +optional
+ Client *ApisixMutualTlsClientConfig `json:"client,omitempty" yaml:"client,omitempty"`
}
// ApisixSecret describes the Kubernetes Secret name and namespace.
type ApisixSecret struct {
- Name string `json:"name,omitempty" yaml:"name,omitempty"`
- Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
+ // +kubebuilder:validation:MinLength=1
+ // +kubebuilder:validation:Required
+ Name string `json:"name" yaml:"name"`
+ // +kubebuilder:validation:MinLength=1
+ // +kubebuilder:validation:Required
+ Namespace string `json:"namespace" yaml:"namespace"`
+}
+
+// ApisixMutualTlsClientConfig describes the mutual TLS CA and verify depth
+type ApisixMutualTlsClientConfig struct {
+ CASecret ApisixSecret `json:"caSecret,omitempty" yaml:"caSecret,omitempty"`
+ Depth int `json:"depth,omitempty" yaml:"depth,omitempty"`
}
diff --git a/pkg/kube/apisix/apis/config/v1/zz_generated.deepcopy.go b/pkg/kube/apisix/apis/config/v1/zz_generated.deepcopy.go
index 1927a84..c0f4d32 100644
--- a/pkg/kube/apisix/apis/config/v1/zz_generated.deepcopy.go
+++ b/pkg/kube/apisix/apis/config/v1/zz_generated.deepcopy.go
@@ -96,6 +96,23 @@
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ApisixMutualTlsClientConfig) DeepCopyInto(out *ApisixMutualTlsClientConfig) {
+ *out = *in
+ out.CASecret = in.CASecret
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApisixMutualTlsClientConfig.
+func (in *ApisixMutualTlsClientConfig) DeepCopy() *ApisixMutualTlsClientConfig {
+ if in == nil {
+ return nil
+ }
+ out := new(ApisixMutualTlsClientConfig)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApisixRoute) DeepCopyInto(out *ApisixRoute) {
*out = *in
out.TypeMeta = in.TypeMeta
@@ -268,10 +285,15 @@
*out = *in
if in.Hosts != nil {
in, out := &in.Hosts, &out.Hosts
- *out = make([]string, len(*in))
+ *out = make([]HostType, len(*in))
copy(*out, *in)
}
out.Secret = in.Secret
+ if in.Client != nil {
+ in, out := &in.Client, &out.Client
+ *out = new(ApisixMutualTlsClientConfig)
+ **out = **in
+ }
return
}
diff --git a/pkg/kube/translation/apisix_ssl.go b/pkg/kube/translation/apisix_ssl.go
index 3dd4f30..7ab54fa 100644
--- a/pkg/kube/translation/apisix_ssl.go
+++ b/pkg/kube/translation/apisix_ssl.go
@@ -19,7 +19,6 @@
"github.com/apache/apisix-ingress-controller/pkg/id"
configv1 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v1"
- apisix "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
)
@@ -44,8 +43,10 @@
return nil, ErrEmptyPrivKey
}
var snis []string
- snis = append(snis, tls.Spec.Hosts...)
- ssl := &apisix.Ssl{
+ for _, host := range tls.Spec.Hosts {
+ snis = append(snis, string(host))
+ }
+ ssl := &apisixv1.Ssl{
ID: id.GenID(tls.Namespace + "_" + tls.Name),
Snis: snis,
Cert: string(cert),
@@ -55,5 +56,20 @@
"managed-by": "apisix-ingress-controller",
},
}
+ if tls.Spec.Client != nil {
+ caSecret, err := t.SecretLister.Secrets(tls.Spec.Client.CASecret.Namespace).Get(tls.Spec.Client.CASecret.Name)
+ if err != nil {
+ return nil, err
+ }
+ ca, ok := caSecret.Data["cert"]
+ if !ok {
+ return nil, ErrEmptyCert
+ }
+ ssl.Client = &apisixv1.MutualTLSClientConfig{
+ CA: string(ca),
+ Depth: tls.Spec.Client.Depth,
+ }
+ }
+
return ssl, nil
}
diff --git a/pkg/types/apisix/v1/types.go b/pkg/types/apisix/v1/types.go
index 393b461..467872e 100644
--- a/pkg/types/apisix/v1/types.go
+++ b/pkg/types/apisix/v1/types.go
@@ -295,12 +295,20 @@
// Ssl apisix ssl object
// +k8s:deepcopy-gen=true
type Ssl struct {
- ID string `json:"id,omitempty" yaml:"id,omitempty"`
- Snis []string `json:"snis,omitempty" yaml:"snis,omitempty"`
- Cert string `json:"cert,omitempty" yaml:"cert,omitempty"`
- Key string `json:"key,omitempty" yaml:"key,omitempty"`
- Status int `json:"status,omitempty" yaml:"status,omitempty"`
- Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
+ ID string `json:"id,omitempty" yaml:"id,omitempty"`
+ Snis []string `json:"snis,omitempty" yaml:"snis,omitempty"`
+ Cert string `json:"cert,omitempty" yaml:"cert,omitempty"`
+ Key string `json:"key,omitempty" yaml:"key,omitempty"`
+ Status int `json:"status,omitempty" yaml:"status,omitempty"`
+ Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
+ Client *MutualTLSClientConfig `json:"client,omitempty" yaml:"client,omitempty"`
+}
+
+// MutualTLSClientConfig apisix SSL client field
+// +k8s:deepcopy-gen=true
+type MutualTLSClientConfig struct {
+ CA string `json:"ca,omitempty" yaml:"ca,omitempty"`
+ Depth int `json:"depth,omitempty" yaml:"depth,omitempty"`
}
// StreamRoute represents the stream_route object in APISIX.
diff --git a/pkg/types/apisix/v1/zz_generated.deepcopy.go b/pkg/types/apisix/v1/zz_generated.deepcopy.go
index 911eb86..64fcdba 100644
--- a/pkg/types/apisix/v1/zz_generated.deepcopy.go
+++ b/pkg/types/apisix/v1/zz_generated.deepcopy.go
@@ -174,6 +174,22 @@
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *MutualTLSClientConfig) DeepCopyInto(out *MutualTLSClientConfig) {
+ *out = *in
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MutualTLSClientConfig.
+func (in *MutualTLSClientConfig) DeepCopy() *MutualTLSClientConfig {
+ if in == nil {
+ return nil
+ }
+ out := new(MutualTLSClientConfig)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RedirectConfig) DeepCopyInto(out *RedirectConfig) {
*out = *in
return
@@ -276,6 +292,11 @@
(*out)[key] = val
}
}
+ if in.Client != nil {
+ in, out := &in.Client, &out.Client
+ *out = new(MutualTLSClientConfig)
+ **out = **in
+ }
return
}
diff --git a/samples/deploy/crd/v1beta1/ApisixTls.yaml b/samples/deploy/crd/v1beta1/ApisixTls.yaml
index b60eea7..c4e1858 100644
--- a/samples/deploy/crd/v1beta1/ApisixTls.yaml
+++ b/samples/deploy/crd/v1beta1/ApisixTls.yaml
@@ -21,23 +21,23 @@
name: apisixtlses.apisix.apache.org
spec:
additionalPrinterColumns:
- - JSONPath: .spec.hosts
- name: SNIs
- type: string
- - JSONPath: .spec.secret.name
- name: Secret Name
- type: string
- - JSONPath: .spec.secret.namespace
- name: Secret Namespace
- type: string
- - JSONPath: .metadata.creationTimestamp
- name: Age
- type: date
+ - JSONPath: .spec.hosts
+ name: SNIs
+ type: string
+ - JSONPath: .spec.secret.name
+ name: Secret Name
+ type: string
+ - JSONPath: .spec.secret.namespace
+ name: Secret Namespace
+ type: string
+ - JSONPath: .metadata.creationTimestamp
+ name: Age
+ type: date
group: apisix.apache.org
versions:
- - name: v1
- served: true
- storage: true
+ - name: v1
+ served: true
+ storage: true
scope: Namespaced
names:
plural: apisixtlses
@@ -50,25 +50,61 @@
status: {}
validation:
openAPIV3Schema:
+ description: ApisixTls defines SSL resource in APISIX.
type: object
properties:
+ apiVersion:
+ description: 'APIVersion defines the versioned schema of this representation
+ of an object. Servers should convert recognized schemas to the latest
+ internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+ type: string
+ kind:
+ description: 'Kind is a string value representing the REST resource this
+ object represents. Servers may infer this from the endpoint the client
+ submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+ type: string
+ metadata:
+ type: object
spec:
+ description: ApisixTlsSpec is the specification of ApisixSSL.
type: object
required:
- - hosts
- - secret
+ - hosts
+ - secret
properties:
+ client:
+ description: ApisixMutualTlsClientConfig describes the mutual TLS CA
+ and verify depth
+ type: object
+ properties:
+ caSecret:
+ description: ApisixSecret describes the Kubernetes Secret name and
+ namespace.
+ type: object
+ required:
+ - name
+ - namespace
+ properties:
+ name:
+ type: string
+ minLength: 1
+ namespace:
+ type: string
+ minLength: 1
+ depth:
+ type: integer
hosts:
type: array
minItems: 1
items:
type: string
- pattern: "^\\*?[0-9a-zA-Z-.]+$"
+ pattern: ^\*?[0-9a-zA-Z-.]+$
secret:
+ description: ApisixSecret describes the Kubernetes Secret name and namespace.
type: object
required:
- - name
- - namespace
+ - name
+ - namespace
properties:
name:
type: string
@@ -76,3 +112,76 @@
namespace:
type: string
minLength: 1
+ status:
+ description: ApisixStatus is the status report for Apisix ingress Resources
+ type: object
+ properties:
+ conditions:
+ type: array
+ items:
+ description: "Condition contains details for one aspect of the current
+ state of this API Resource. --- This struct is intended for direct
+ use as an array at the field path .status.conditions. For example,
+ type FooStatus struct{ // Represents the observations of a foo's
+ current state. // Known .status.conditions.type are: \"Available\",
+ \"Progressing\", and \"Degraded\" // +patchMergeKey=type //
+ +patchStrategy=merge // +listType=map // +listMapKey=type
+ \ Conditions []metav1.Condition `json:\"conditions,omitempty\"
+ patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`
+ \n // other fields }"
+ type: object
+ required:
+ - lastTransitionTime
+ - message
+ - reason
+ - status
+ - type
+ properties:
+ lastTransitionTime:
+ description: lastTransitionTime is the last time the condition
+ transitioned from one status to another. This should be when
+ the underlying condition changed. If that is not known, then
+ using the time when the API field changed is acceptable.
+ type: string
+ format: date-time
+ message:
+ description: message is a human readable message indicating details
+ about the transition. This may be an empty string.
+ type: string
+ maxLength: 32768
+ observedGeneration:
+ description: observedGeneration represents the .metadata.generation
+ that the condition was set based upon. For instance, if .metadata.generation
+ is currently 12, but the .status.conditions[x].observedGeneration
+ is 9, the condition is out of date with respect to the current
+ state of the instance.
+ type: integer
+ format: int64
+ minimum: 0
+ reason:
+ description: reason contains a programmatic identifier indicating
+ the reason for the condition's last transition. Producers of
+ specific condition types may define expected values and meanings
+ for this field, and whether the values are considered a guaranteed
+ API. The value should be a CamelCase string. This field may
+ not be empty.
+ type: string
+ maxLength: 1024
+ minLength: 1
+ pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
+ status:
+ description: status of the condition, one of True, False, Unknown.
+ type: string
+ enum:
+ - "True"
+ - "False"
+ - Unknown
+ type:
+ description: type of condition in CamelCase or in foo.example.com/CamelCase.
+ --- Many .condition.type values are consistent across resources
+ like Available, but because arbitrary conditions can be useful
+ (see .node.status.conditions), the ability to deconflict is
+ important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
+ type: string
+ maxLength: 316
+ pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
diff --git a/test/e2e/ingress/secret.go b/test/e2e/ingress/secret.go
index 2c19594..421a6a2 100644
--- a/test/e2e/ingress/secret.go
+++ b/test/e2e/ingress/secret.go
@@ -26,6 +26,7 @@
"github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
)
+// TODO: FIXME
var _ = ginkgo.Describe("secret Testing", func() {
opts := &scaffold.Options{
Name: "default",
@@ -153,7 +154,7 @@
assert.Nil(ginkgo.GinkgoT(), err, "create tls error")
// check ssl in APISIX
time.Sleep(10 * time.Second)
- tls, err := s.ListApisixTls()
+ tls, err := s.ListApisixSsl()
assert.Nil(ginkgo.GinkgoT(), err, "list tls error")
assert.Len(ginkgo.GinkgoT(), tls, 1, "tls number not expect")
assert.Equal(ginkgo.GinkgoT(), cert, tls[0].Cert, "tls cert not expect")
@@ -245,17 +246,17 @@
8Z1U5/a5LhrDtIKAsCCpRx99P++Eqt2M2YV7jZcTfEbEvxP4XBYcdh30nbq1uEhs
4zEnK1pMx5PnEljN1mcgmL2TPsMVN5DN9zXHW5eNQ6wfXR8rCfHwVIVcUuaB
-----END RSA PRIVATE KEY-----`
- key_compare_update := "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"
+ keyCompareUpdate := "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"
// key update compare
err = s.NewSecret(secretName, certUpdate, keyUpdate)
assert.Nil(ginkgo.GinkgoT(), err, "create secret error")
// check ssl in APISIX
time.Sleep(10 * time.Second)
- tlsUpdate, err := s.ListApisixTls()
+ tlsUpdate, err := s.ListApisixSsl()
assert.Nil(ginkgo.GinkgoT(), err, "list tlsUpdate error")
assert.Len(ginkgo.GinkgoT(), tlsUpdate, 1, "tls number not expect")
assert.Equal(ginkgo.GinkgoT(), certUpdate, tlsUpdate[0].Cert, "tls cert not expect")
- assert.Equal(ginkgo.GinkgoT(), key_compare_update, tlsUpdate[0].Key, "tls key not expect")
+ assert.Equal(ginkgo.GinkgoT(), keyCompareUpdate, tlsUpdate[0].Key, "tls key not expect")
// check DP
s.NewAPISIXHttpsClient(host).GET("/ip").WithHeader("Host", host).Expect().Status(http.StatusOK).Body().Raw()
@@ -264,7 +265,7 @@
assert.Nil(ginkgo.GinkgoT(), err, "delete tls error")
// check ssl in APISIX
time.Sleep(10 * time.Second)
- tls, err = s.ListApisixTls()
+ tls, err = s.ListApisixSsl()
assert.Nil(ginkgo.GinkgoT(), err, "list tls error")
assert.Len(ginkgo.GinkgoT(), tls, 0, "tls number not expect")
})
diff --git a/test/e2e/ingress/ssl.go b/test/e2e/ingress/ssl.go
index 5511a86..ba8fe7f 100644
--- a/test/e2e/ingress/ssl.go
+++ b/test/e2e/ingress/ssl.go
@@ -16,6 +16,10 @@
package ingress
import (
+ "crypto/tls"
+ "crypto/x509"
+ "fmt"
+ "net/http"
"time"
"github.com/onsi/ginkgo"
@@ -85,7 +89,7 @@
assert.Nil(ginkgo.GinkgoT(), err, "create tls error")
// check ssl in APISIX
time.Sleep(10 * time.Second)
- tls, err := s.ListApisixTls()
+ tls, err := s.ListApisixSsl()
assert.Nil(ginkgo.GinkgoT(), err, "list tls error")
assert.Len(ginkgo.GinkgoT(), tls, 1, "tls number not expect")
})
@@ -153,7 +157,7 @@
// check ssl in APISIX
time.Sleep(10 * time.Second)
- tls, err := s.ListApisixTls()
+ tls, err := s.ListApisixSsl()
assert.Nil(ginkgo.GinkgoT(), err, "list tls error")
assert.Len(ginkgo.GinkgoT(), tls, 1, "tls number not expect")
assert.Equal(ginkgo.GinkgoT(), tls[0].Snis[0], host, "tls host is error")
@@ -221,7 +225,7 @@
// check ssl in APISIX
time.Sleep(10 * time.Second)
- tls, err := s.ListApisixTls()
+ tls, err := s.ListApisixSsl()
assert.Nil(ginkgo.GinkgoT(), err, "list tls error")
assert.Len(ginkgo.GinkgoT(), tls, 1, "tls number not expect")
assert.Equal(ginkgo.GinkgoT(), tls[0].Snis[0], host, "tls host is error")
@@ -231,8 +235,290 @@
assert.Nil(ginkgo.GinkgoT(), err, "delete tls error")
// check ssl in APISIX
time.Sleep(10 * time.Second)
- tls, err = s.ListApisixTls()
+ tls, err = s.ListApisixSsl()
assert.Nil(ginkgo.GinkgoT(), err, "list tls error")
assert.Len(ginkgo.GinkgoT(), tls, 0, "tls number not expect")
})
})
+
+var _ = ginkgo.Describe("ApisixTls mTLS Test", func() {
+ // RootCA -> Server
+ // RootCA -> UserCert
+ // These certs come from mTLS practice
+
+ rootCA := `-----BEGIN CERTIFICATE-----
+MIIF9zCCA9+gAwIBAgIUFKuzAJZgm/fsFS6JDrd+lcpVZr8wDQYJKoZIhvcNAQEL
+BQAwgZwxCzAJBgNVBAYTAkNOMREwDwYDVQQIDAhaaGVqaWFuZzERMA8GA1UEBwwI
+SGFuZ3pob3UxGDAWBgNVBAoMD0FQSVNJWC1UZXN0LUNBXzEYMBYGA1UECwwPQVBJ
+U0lYX0NBX1JPT1RfMRUwEwYDVQQDDAxBUElTSVguUk9PVF8xHDAaBgkqhkiG9w0B
+CQEWDXRlc3RAdGVzdC5jb20wHhcNMjEwNTI3MTMzNjI4WhcNMjIwNTI3MTMzNjI4
+WjCBnDELMAkGA1UEBhMCQ04xETAPBgNVBAgMCFpoZWppYW5nMREwDwYDVQQHDAhI
+YW5nemhvdTEYMBYGA1UECgwPQVBJU0lYLVRlc3QtQ0FfMRgwFgYDVQQLDA9BUElT
+SVhfQ0FfUk9PVF8xFTATBgNVBAMMDEFQSVNJWC5ST09UXzEcMBoGCSqGSIb3DQEJ
+ARYNdGVzdEB0ZXN0LmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
+ALJR0lQW/IBqQTE/Oa0Pi4LlmlYUSGnqtFNqiZyOF0PjVzNeqoD9JDPiM1QRyC8p
+NCd5L/QhtUIMMx0RlDI9DkJ3ALIWdrPIZlwpveDJf4KtW7cz+ea46A6QQwB6xcyV
+xWnqEBkiea7qrEE8NakZOMjgkqkN2/9klg6XyA5FWfvszxtuIHtjcy2Kq8bMC0jd
+k7CqEZe4ct6s2wlcI8t8s9prvMDm8gcX66x4Ah+C2/W+C3lTpMDgGqRqSPyCW7na
+Wgn0tWmTSf1iybwYMydhC+zpM1QJLvfDyqjp1wJhziR5ttVe2Xc+tDC24s+u16yZ
+R93IO0M4lLNjvEKJcMltXyRzrcjvLXOhw3KirSHNL1KfrBEl74lb+DV5eU4pIFCj
+cu18gms5FBYs9tpLujwpHDc2MU+zCvRmSPvUA4yCyoXqom3uiSo3g3ymW9IM8dC8
++Bd1GdM6JbpBukvQybc5TQXo1M75I9iEoQa5tQxAfQ/dfwMjOK7skogowBouOuLv
+BEFKy3Vd57IWWZXC4p/74M6N4fGYTgHY5FQE3R4Y2phk/eaEm1jS1UPuC98QuTfL
+rGuFOIBmK5euOm8uT5m9hnrouG2ZcxEdzHYfjsGDGrLzA0FLu+wtMNBKM4NhsNCa
+d+fycLg7jgxWhaLvD5DfkV7WFQlz5LUceYIwYOyhD/chAgMBAAGjLzAtMAwGA1Ud
+EwQFMAMBAf8wHQYDVR0RBBYwFIISbXRscy5odHRwYmluLmxvY2FsMA0GCSqGSIb3
+DQEBCwUAA4ICAQCNtBmoAc5tv3H38sj9qhTmabvp9RIzZYrQSEcN+A2i3a8FVYAM
+YaugZDXDcTycoWn6rcgblUDneow3NiqZ57yYZmN+e4mE3+Q1sGepV7LoRkHDUT8w
+jAJndcZ/xxJmgH6B7dImTAPsvLGR7E7gffMH+aKCdnkG9x5Vm+cuBwSEBndiHGfr
+yw5cXO6cMUq8M6zJrk2V+1BAucXW2rgLTWy6UTTGD56cgUtbStRO6muOKoElDLbW
+mSj2rNv/evakQkV8dgKVRFgh2NQKYKpXmveMaE6xtFFf/dd9OhDFjUh/ksxn94FT
+xj/wkhXCEPl+t7tENhr2tNyLbCOVcFzqoi7IyoWKxxZQfvArfj4SmahK8E/BXB/T
+4PEmn8kZAxaW7RmGcaekm8MTqGlhCJ3tVJAI2vcYRdd9ZHbXE1jr/4xj0I/Lzglo
+O8v5fd4zHyV1SuZ5AH3XbUd7ndl9yDoN2WSqK9Nd9bws3yrf+GwjJAT1InnDvLg1
+stWM8I+9FZiDFL255/+iAN0jYcGu9i4TNvC+o6qQ1p85i1OHPJZu6wtUWMgDJN46
+uwW3ZLh9sZV6OnhbQJBQaUmcgaPJUQqbXNQmpmpc0NUjET/ltFRZ2hlyvvpf7wwF
+2DLY1HRAknQ69DuT6xpYz1aKZqrlkbCWlMMvdosOg6f7+4NxdYJ/rBeS6Q==
+-----END CERTIFICATE-----
+`
+
+ serverCertSecret := `server-secret`
+ serverCert := `-----BEGIN CERTIFICATE-----
+MIIF/TCCA+WgAwIBAgIUBbUP7Gk0WAb/JhYYcBBgZEgmhbEwDQYJKoZIhvcNAQEL
+BQAwgZwxCzAJBgNVBAYTAkNOMREwDwYDVQQIDAhaaGVqaWFuZzERMA8GA1UEBwwI
+SGFuZ3pob3UxGDAWBgNVBAoMD0FQSVNJWC1UZXN0LUNBXzEYMBYGA1UECwwPQVBJ
+U0lYX0NBX1JPT1RfMRUwEwYDVQQDDAxBUElTSVguUk9PVF8xHDAaBgkqhkiG9w0B
+CQEWDXRlc3RAdGVzdC5jb20wHhcNMjEwNTI3MTMzNjI5WhcNMjIwNTI3MTMzNjI5
+WjCBpTELMAkGA1UEBhMCQ04xETAPBgNVBAgMCFpoZWppYW5nMREwDwYDVQQHDAhI
+YW5nemhvdTEcMBoGA1UECgwTQVBJU0lYLVRlc3QtU2VydmVyXzEXMBUGA1UECwwO
+QVBJU0lYX1NFUlZFUl8xGzAZBgNVBAMMEm10bHMuaHR0cGJpbi5sb2NhbDEcMBoG
+CSqGSIb3DQEJARYNdGVzdEB0ZXN0LmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIP
+ADCCAgoCggIBAMZRPG8gUrOGM4awnV6D8Ds0Xb6jVbiGkx+1YsvPx5oIE4AswJ0l
+y6zqhBFnpQozFG63KfsCA6U36/Dty3rIbJzsbO7YaOMJItoiQgqdqF2nrmPpmpCQ
+uLGKaVvriRCD55NEmFQPshlRfcU5/EEreNKbRve3zEKHRpCDBZ2Myvrpt3CCVy6D
+MbLllbjUvaedrnQxlmI5d7x3UCe4Eunq8vn7c0p4frA1n8TxbX0M4Yr9g3YEEqCv
+Q3/9jU4hI5CvujCp+u79EavJZfsaEv3RYgHkoEh7q+OEkUajWXKj4WynizraWsHv
++LvK9pfI300p1HSKK4FqonvW79anRNbK+8BqV4Wt5aBeFU/rW2jHtJxcl1OLRrrh
+wftCP5W7vSjvJes5wPDZjDEyv8WP1Aa6yWeGHHtIwrAHPr7556F/JAQS6IPBQQ5U
+X45DD2aNXME9xZKdBtyMovItjZm31UUsvoF+YtpAOmbEkX4lMznUO3XZJjM5HWSq
+WYyzmFsw+pJEwhXRo4GfSfCHfiZQ6imTLJ7OsZzo9bvmxyfI0kVLe3h3iCe+qYeT
+f5AJU6v5vv3thCtfgbxYP2P8b+0MIrfr05e6dCDXbIri1z+nprzWYmyCrZ6H4hVk
+DzMktFUlkqenvnsJ2iOV2AZw0Hlk2bwe4zSumzqoIp8Yk/kxbfxhQqr5AgMBAAGj
+LDAqMAkGA1UdEwQCMAAwHQYDVR0RBBYwFIISbXRscy5odHRwYmluLmxvY2FsMA0G
+CSqGSIb3DQEBCwUAA4ICAQCDDfETCEpWB/KRQZo2JF8n4NEDTeraQ85M3H5luJHp
+NdJO4oYq3n8B149ep4FcEYdO20pV+TMeMNWXMfhoRIpGx95JrLuLg6qnw6eNdErn
+YupHMC2OEoEWVcmI052LDJcXuKsTXQvU4OeEL2dX4OtNJ+mRODLyh40cg7dA3wry
+kGLiprRlLQtiX8pSDG30qPZexL1LcFzBQajriG05QUrJW6Rvbq1JTIlyp7E1T86f
+Xljq0Hdzqxy+FklYcAW5ZAxgkQlMmVdTlvDXlD/hQLEQIHGHiW6OMLp8WrnJP6b0
+D2HqWmOwuEzqSgXSK0N89rpiWP1FKCpyiKVcsawDNfOpePVuthommVEc2PxacyHf
+UCC9V0MS0ZzQ63Tnz2Tja8C6/kMyVX226KQKhcoDxDoS0mQrI96/VXcglwP5hMjF
+joth1T1qRVu6+NQmvFPaNjbzWJ+j1R99bnYGihPeLdqDSUxNosV3ULG8T4aN6+f8
+hApiqg2dkLJQr8zWf6vWXMlREdPEovb2F7P0Lfn0VeOSRXDUIdqcoRHONi8bWMRs
+fjPtGW00Tv8Jg21c9vc8Zh/t1w3wkXQhqYiBMt5cYe6WueIlXdjF7ikSRWAHTwlw
+Bfzv/vMftLnbySPovCzQ1PF55D01EWRk0o6PRwUDLfzTQoV+bDKx82LxKtZBtQEX
+uw==
+-----END CERTIFICATE-----
+`
+ serverKey := `-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----
+`
+ clientCASecret := `client-ca-secret`
+ clientCert := `-----BEGIN CERTIFICATE-----
+MIIGDDCCA/SgAwIBAgIUBbUP7Gk0WAb/JhYYcBBgZEgmhbIwDQYJKoZIhvcNAQEL
+BQAwgZwxCzAJBgNVBAYTAkNOMREwDwYDVQQIDAhaaGVqaWFuZzERMA8GA1UEBwwI
+SGFuZ3pob3UxGDAWBgNVBAoMD0FQSVNJWC1UZXN0LUNBXzEYMBYGA1UECwwPQVBJ
+U0lYX0NBX1JPT1RfMRUwEwYDVQQDDAxBUElTSVguUk9PVF8xHDAaBgkqhkiG9w0B
+CQEWDXRlc3RAdGVzdC5jb20wHhcNMjEwNTI3MTMzNjMxWhcNMjIwNTI3MTMzNjMx
+WjCBtDELMAkGA1UEBhMCQ04xETAPBgNVBAgMCFpoZWppYW5nMREwDwYDVQQHDAhI
+YW5nemhvdTEfMB0GA1UECgwWQVBJU0lYLVRlc3QtUm9vdC1Vc2VyXzEaMBgGA1UE
+CwwRQVBJU0lYX1JPT1RfVVNFUl8xJDAiBgNVBAMMG0JpZ01pbmdfIDY1NDMxMTEx
+MTExMTExMTExMTEcMBoGCSqGSIb3DQEJARYNdGVzdEB0ZXN0LmNvbTCCAiIwDQYJ
+KoZIhvcNAQEBBQADggIPADCCAgoCggIBAL41i+W8fgqtKButnkOa0qGApPJ7HSUI
+gxBt6Tb7mR8eka8vno8hIE42hwMXYoIiO1FgM9M9fPN8vzTnWr2iTAXbw/qXdn14
+RT01JrGzj1OU3M9U0RMmDWVs8VOY3ncdcdPoqctMNr2K+6wlN6EpG1DQDUEvB0Yy
+qZpFWpuZP0akzjFqeJNSo3b8hP4J1nsWBxKt6TRInC60jT1mhKCjTB1IsXVC+5SY
+f0FhMN2ZQPgCyIIq+j5S7tHta4Qs+UDNY0whIMBkY3ZAGPzUBFExwrX7bGNbeJYz
+DahNAKiEj5dlieRvzTn5UisSv5EzDUONLSluEnCU1Ghd/dfZtZwlMBU6OIflwm6W
+X7HePD0G8XEDe5qbU/tg/62j+A1hMS2gSlibNisyvNwMNz6Imc3CnIFUL2mTsgl1
+bCBLfOXDfrjte5Txnt26ayN0+WhmVSts0+ln6vnGtgVGqH0bmp95yMRYPej+6ARE
+MtfvrNA7iZORypspv2NVyV11b6Jx3o/pJOAq9dmAsKvJ6hK0IVf/55t3ZtzRa/fY
+w0f78KbWuEc7qmx2tBxnEfppb5iB/RFmI2bDaAEA0lDQgAqmiAu1utetfqsCwA1m
+3VXGp8Vp+lexA/M3Bkb5BHa6C11QDHvGjUVwduOymLAHKumvdgf7TJFnlhpg6jYN
+unO58bRSTI6LAgMBAAGjLDAqMAkGA1UdEwQCMAAwHQYDVR0RBBYwFIISbXRscy5o
+dHRwYmluLmxvY2FsMA0GCSqGSIb3DQEBCwUAA4ICAQCoTBvw11aKah2cuB4XplUB
+nmkrWhfLNFJLVrU9jIISP9Wkl3s3PcM+aEWygb/1roqbMqNOgrzDVgGRRFCiS6qi
+himUuTJhIBI6TF1PE+XW3gTFWBXkAZ7MzpbS8oP1PehlY3XXKNZgxZi3XaDI8Hfw
+5MWBGNbk8tegn8bvYQUz2VxmCo6zufCkj4ADjw2zhiyKBKuHTzg48w66Wn4jLhlK
+p91HHrK0lEOIJ4pFmBUpBsSJMlBMbfrzBF87xQhpDO3ScFfCWUatShmXsPMJU0F0
+DEuTnaHUefUf/F9wUGNcA4yQ4pH8SxVpRHmrWE8U4uSXpz1bx4ChZurJ9mPzrj9h
+U9c/d9F5WndZNPcR1R8Tbzhk/R8GImVR3Lt59cW+SN/+4JVFy+Hye2yslGFn2CAJ
+ofNxjLb9OE6+EE3SWW0B5CZSWBS49gtdTW0ApOjIRJU2zipxcjnNf00GFoIoCxjk
+Z4eBQz9WVUM9KSrJIQSLZQd35tZAOp0BuwWho0+w8lXUchSqT7oRA7+szZldWF0j
+HKPIMJ0iVWmXuZjsS8q8NBIt4DuBcqpevlol5KRXv6tJy4IBVAVEIBdeXotvdxKE
+bncvZ6xo9A/waUU7tEyzv34usxefrWxtSlOA1G0Jj4nb5gKPHjn0XIr9WI2RpovT
+/XpB6QES1zoBQya3QjnDbQ==
+-----END CERTIFICATE-----
+`
+
+ clientKey := `-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----
+`
+
+ s := scaffold.NewDefaultV2Scaffold()
+ ginkgo.It("create a SSL with client CA", func() {
+ // create secrets
+ err := s.NewSecret(serverCertSecret, serverCert, serverKey)
+ assert.Nil(ginkgo.GinkgoT(), err, "create server cert secret error")
+ err = s.NewClientCASecret(clientCASecret, rootCA, "")
+ assert.Nil(ginkgo.GinkgoT(), err, "create client CA cert secret error")
+
+ // create ApisixTls resource
+ tlsName := "tls-with-client-ca"
+ host := "mtls.httpbin.local"
+ err = s.NewApisixTlsWithClientCA(tlsName, host, serverCertSecret, clientCASecret)
+ assert.Nil(ginkgo.GinkgoT(), err, "create ApisixTls with client CA error")
+ // check ssl in APISIX
+ time.Sleep(10 * time.Second)
+ apisixSsls, err := s.ListApisixSsl()
+ assert.Nil(ginkgo.GinkgoT(), err, "list ssl error")
+ assert.Len(ginkgo.GinkgoT(), apisixSsls, 1, "ssl number not expect")
+
+ // create route
+ backendSvc, backendSvcPort := s.DefaultHTTPBackend()
+ apisixRoute := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2alpha1
+kind: ApisixRoute
+metadata:
+ name: httpbin-route
+spec:
+ http:
+ - name: rule1
+ match:
+ hosts:
+ - mtls.httpbin.local
+ paths:
+ - /*
+ backend:
+ serviceName: %s
+ servicePort: %d
+`, backendSvc, backendSvcPort[0])
+ assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(apisixRoute))
+ time.Sleep(10 * time.Second)
+
+ apisixRoutes, err := s.ListApisixRoutes()
+ assert.Nil(ginkgo.GinkgoT(), err, "list routes error")
+ assert.Len(ginkgo.GinkgoT(), apisixRoutes, 1, "route number not expect")
+
+ // Without Client Cert
+ s.NewAPISIXHttpsClient(host).GET("/ip").WithHeader("Host", host).Expect().Status(http.StatusBadRequest).Body().Raw()
+
+ // With client cert
+ caCertPool := x509.NewCertPool()
+ ok := caCertPool.AppendCertsFromPEM([]byte(rootCA))
+ assert.True(ginkgo.GinkgoT(), ok, "Append cert to CA pool")
+
+ cert, err := tls.X509KeyPair([]byte(clientCert), []byte(clientKey))
+ assert.Nil(ginkgo.GinkgoT(), err, "generate cert")
+
+ s.NewAPISIXHttpsClientWithCertificates(host, true, caCertPool, []tls.Certificate{cert}).
+ GET("/ip").WithHeader("Host", host).Expect().Status(http.StatusOK)
+ })
+})
diff --git a/test/e2e/scaffold/k8s.go b/test/e2e/scaffold/k8s.go
index da5ede6..4f4a912 100644
--- a/test/e2e/scaffold/k8s.go
+++ b/test/e2e/scaffold/k8s.go
@@ -305,8 +305,8 @@
return cli.Cluster("").StreamRoute().List(context.TODO())
}
-// ListApisixTls list all ssl from APISIX
-func (s *Scaffold) ListApisixTls() ([]*v1.Ssl, error) {
+// ListApisixSsl list all ssl from APISIX
+func (s *Scaffold) ListApisixSsl() ([]*v1.Ssl, error) {
u := url.URL{
Scheme: "http",
Host: s.apisixAdminTunnel.Endpoint(),
diff --git a/test/e2e/scaffold/scaffold.go b/test/e2e/scaffold/scaffold.go
index 5e68ff5..2e0998c 100644
--- a/test/e2e/scaffold/scaffold.go
+++ b/test/e2e/scaffold/scaffold.go
@@ -17,6 +17,7 @@
import (
"context"
"crypto/tls"
+ "crypto/x509"
"fmt"
"io/ioutil"
"net/http"
@@ -126,6 +127,19 @@
return NewScaffold(opts)
}
+// NewDefaultV2Scaffold creates a scaffold with some default options.
+func NewDefaultV2Scaffold() *Scaffold {
+ opts := &Options{
+ Name: "default",
+ Kubeconfig: GetKubeconfig(),
+ APISIXConfigPath: "testdata/apisix-gw-config.yaml",
+ IngressAPISIXReplicas: 1,
+ HTTPBinServicePort: 80,
+ APISIXRouteVersion: kube.ApisixRouteV2alpha1,
+ }
+ return NewScaffold(opts)
+}
+
// KillPod kill the pod which name is podName.
func (s *Scaffold) KillPod(podName string) error {
cli, err := k8s.GetKubernetesClientE(s.t)
@@ -191,7 +205,7 @@
})
}
-// NewAPISIXHttpsClient creates the default HTTPs client.
+// NewAPISIXHttpsClient creates the default HTTPS client.
func (s *Scaffold) NewAPISIXHttpsClient(host string) *httpexpect.Expect {
u := url.URL{
Scheme: "https",
@@ -214,6 +228,30 @@
})
}
+// NewAPISIXHttpsClientWithCertificates creates the default HTTPS client with giving trusted CA and client certs.
+func (s *Scaffold) NewAPISIXHttpsClientWithCertificates(host string, insecure bool, ca *x509.CertPool, certs []tls.Certificate) *httpexpect.Expect {
+ u := url.URL{
+ Scheme: "https",
+ Host: s.apisixHttpsTunnel.Endpoint(),
+ }
+ return httpexpect.WithConfig(httpexpect.Config{
+ BaseURL: u.String(),
+ Client: &http.Client{
+ Transport: &http.Transport{
+ TLSClientConfig: &tls.Config{
+ InsecureSkipVerify: insecure,
+ ServerName: host,
+ RootCAs: ca,
+ Certificates: certs,
+ },
+ },
+ },
+ Reporter: httpexpect.NewAssertReporter(
+ httpexpect.NewAssertReporter(ginkgo.GinkgoT()),
+ ),
+ })
+}
+
// APISIXGatewayServiceEndpoint returns the apisix http gateway endpoint.
func (s *Scaffold) APISIXGatewayServiceEndpoint() string {
return s.apisixHttpTunnel.Endpoint()
diff --git a/test/e2e/scaffold/ssl.go b/test/e2e/scaffold/ssl.go
index cdde5ab..4b84134 100644
--- a/test/e2e/scaffold/ssl.go
+++ b/test/e2e/scaffold/ssl.go
@@ -32,6 +32,14 @@
cert: %s
key: %s
`
+ _clientCASecretTemplate = `
+apiVersion: v1
+kind: Secret
+metadata:
+ name: %s
+data:
+ cert: %s
+`
_api6tlsTemplate = `
apiVersion: apisix.apache.org/v1
kind: ApisixTls
@@ -44,6 +52,23 @@
name: %s
namespace: %s
`
+ _api6tlsWithClientCATemplate = `
+apiVersion: apisix.apache.org/v1
+kind: ApisixTls
+metadata:
+ name: %s
+spec:
+ hosts:
+ - %s
+ secret:
+ name: %s
+ namespace: %s
+ client:
+ caSecret:
+ name: %s
+ namespace: %s
+ depth: 10
+`
)
// NewSecret new a k8s secret
@@ -57,6 +82,16 @@
return nil
}
+// NewSecret new a k8s secret
+func (s *Scaffold) NewClientCASecret(name, cert, key string) error {
+ certBase64 := base64.StdEncoding.EncodeToString([]byte(cert))
+ secret := fmt.Sprintf(_clientCASecretTemplate, name, certBase64)
+ if err := k8s.KubectlApplyFromStringE(s.t, s.kubectlOptions, secret); err != nil {
+ return err
+ }
+ return nil
+}
+
// NewApisixTls new a ApisixTls CRD
func (s *Scaffold) NewApisixTls(name, host, secretName string) error {
tls := fmt.Sprintf(_api6tlsTemplate, name, host, secretName, s.kubectlOptions.Namespace)
@@ -66,6 +101,15 @@
return nil
}
+// NewApisixTlsWithClientCA new a ApisixTls CRD
+func (s *Scaffold) NewApisixTlsWithClientCA(name, host, secretName, clientCASecret string) error {
+ tls := fmt.Sprintf(_api6tlsWithClientCATemplate, name, host, secretName, s.kubectlOptions.Namespace, clientCASecret, s.kubectlOptions.Namespace)
+ if err := k8s.KubectlApplyFromStringE(s.t, s.kubectlOptions, tls); err != nil {
+ return err
+ }
+ return nil
+}
+
// DeleteApisixTls remove ApisixTls CRD
func (s *Scaffold) DeleteApisixTls(name string, host, secretName string) error {
tls := fmt.Sprintf(_api6tlsTemplate, name, host, secretName, s.kubectlOptions.Namespace)