You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by kv...@apache.org on 2021/05/31 03:32:19 UTC
[apisix-ingress-controller] branch master updated: feat: ApisixTls
support mTLS (#492)
This is an automated email from the ASF dual-hosted git repository.
kvn pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git
The following commit(s) were added to refs/heads/master by this push:
new 38290a2 feat: ApisixTls support mTLS (#492)
38290a2 is described below
commit 38290a2893b4bf77869b34648aeb8d55dd298537
Author: Sarasa Kisaragi <li...@gmail.com>
AuthorDate: Mon May 31 11:32:13 2021 +0800
feat: ApisixTls support mTLS (#492)
---
.licenserc.yaml | 2 +
docs/en/latest/practices/mtls.md | 222 ++++++++++++++++
docs/en/latest/practices/mtls/ca.pem | 34 +++
.../en/latest/practices/mtls/client-ca-secret.yaml | 21 ++
docs/en/latest/practices/mtls/mtls.yaml | 31 +++
docs/en/latest/practices/mtls/route.yaml | 31 +++
docs/en/latest/practices/mtls/server-secret.yaml | 23 ++
docs/en/latest/practices/mtls/server.key | 51 ++++
docs/en/latest/practices/mtls/server.pem | 35 +++
docs/en/latest/practices/mtls/tls.yaml | 26 ++
docs/en/latest/practices/mtls/user.key | 51 ++++
docs/en/latest/practices/mtls/user.pem | 35 +++
pkg/apisix/ssl.go | 8 +-
pkg/ingress/apisix_tls.go | 26 +-
pkg/ingress/controller.go | 5 +
pkg/ingress/secret.go | 76 +++++-
pkg/kube/apisix/apis/config/v1/types.go | 42 ++-
.../apisix/apis/config/v1/zz_generated.deepcopy.go | 24 +-
pkg/kube/translation/apisix_ssl.go | 22 +-
pkg/types/apisix/v1/types.go | 20 +-
pkg/types/apisix/v1/zz_generated.deepcopy.go | 21 ++
samples/deploy/crd/v1beta1/ApisixTls.yaml | 149 +++++++++--
test/e2e/ingress/secret.go | 11 +-
test/e2e/ingress/ssl.go | 294 ++++++++++++++++++++-
test/e2e/scaffold/k8s.go | 4 +-
test/e2e/scaffold/scaffold.go | 40 ++-
test/e2e/scaffold/ssl.go | 44 +++
27 files changed, 1270 insertions(+), 78 deletions(-)
diff --git a/.licenserc.yaml b/.licenserc.yaml
index ad8836c..070b515 100644
--- a/.licenserc.yaml
+++ b/.licenserc.yaml
@@ -38,4 +38,6 @@ header:
- '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: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUY5ekNDQTkrZ0F3SUJBZ0lVRkt1ekFKWmdtL2ZzRlM2SkRyZCtsY3BWWnI4d0RRWUpLb1pJaHZjTkFRRUwKQlFBd2dad3hDekFKQmdOVkJBWVRBa05PTVJFd0R3WURWUVFJREFoYWFHVnFhV0Z1WnpFUk1BOEdBMVVFQnd3SQpTR0Z1WjNwb2IzVXhHREFXQmdOVkJBb01EMEZRU1ZOSldDMVVaWE4wTFVOQlh6RVlNQllHQTFVRUN3d1BRVkJKClUwbFlYME5CWDFKUFQxUmZNUlV3RXdZRFZRUUREQXhCVUVsVFNWZ3VVazlQVkY4eEhEQWFCZ2txaGtpRzl3MEIKQ1FFV0RYUmxjM1JBZEdWemRDNWpiMjB3SGhjTk1qRXdOVEkzTVRNek5qSTRXaGNOTWpJd05USTNNVE16TmpJNApXakNCbkRFTE1B [...]
+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: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUYvVENDQStXZ0F3SUJBZ0lVQmJVUDdHazBXQWIvSmhZWWNCQmdaRWdtaGJFd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2dad3hDekFKQmdOVkJBWVRBa05PTVJFd0R3WURWUVFJREFoYWFHVnFhV0Z1WnpFUk1BOEdBMVVFQnd3SQpTR0Z1WjNwb2IzVXhHREFXQmdOVkJBb01EMEZRU1ZOSldDMVVaWE4wTFVOQlh6RVlNQllHQTFVRUN3d1BRVkJKClUwbFlYME5CWDFKUFQxUmZNUlV3RXdZRFZRUUREQXhCVUVsVFNWZ3VVazlQVkY4eEhEQWFCZ2txaGtpRzl3MEIKQ1FFV0RYUmxjM1JBZEdWemRDNWpiMjB3SGhjTk1qRXdOVEkzTVRNek5qSTVXaGNOTWpJd05USTNNVE16TmpJNQpXakNCcFRFTE1B [...]
+ key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlKS0FJQkFBS0NBZ0VBeGxFOGJ5QlNzNFl6aHJDZFhvUHdPelJkdnFOVnVJYVRIN1ZpeTgvSG1nZ1RnQ3pBCm5TWExyT3FFRVdlbENqTVVicmNwK3dJRHBUZnI4TzNMZXNoc25PeHM3dGhvNHdraTJpSkNDcDJvWGFldVkrbWEKa0pDNHNZcHBXK3VKRUlQbmswU1lWQSt5R1ZGOXhUbjhRU3Q0MHB0Rzk3Zk1Rb2RHa0lNRm5ZeksrdW0zY0lKWApMb014c3VXVnVOUzlwNTJ1ZERHV1lqbDN2SGRRSjdnUzZlcnkrZnR6U25oK3NEV2Z4UEZ0ZlF6aGl2MkRkZ1FTCm9LOURmLzJOVGlFamtLKzZNS242N3YwUnE4bGwreG9TL2RGaUFlU2dTSHVyNDRTUlJxTlpjcVBoYktlTE90cGEKd2UvNHU4c [...]
+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 @@ func (s *sslClient) Create(ctx context.Context, obj *v1.Ssl) (*v1.Ssl, error) {
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 @@ func (c *apisixTlsController) sync(ctx context.Context, ev *types.Event) error {
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 @@ func (c *apisixTlsController) sync(ctx context.Context, ev *types.Event) error {
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 @@ func (c *Controller) recorderEvent(object runtime.Object, eventtype, reason stri
}
}
+// 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 @@ package ingress
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 @@ func (c *secretController) sync(ctx context.Context, ev *types.Event) error {
// 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 @@ func (c *secretController) sync(ctx context.Context, ev *types.Event) error {
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 @@ import (
// +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 @@ func (p *Config) DeepCopy() *Config {
// +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 @@ func (in *ActiveHealthCheckUnhealthy) DeepCopy() *ActiveHealthCheckUnhealthy {
}
// 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 @@ func (in *ApisixTlsSpec) DeepCopyInto(out *ApisixTlsSpec) {
*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 @@ import (
"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 @@ func (t *translator) TranslateSSL(tls *configv1.ApisixTls) (*apisixv1.Ssl, error
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 @@ func (t *translator) TranslateSSL(tls *configv1.ApisixTls) (*apisixv1.Ssl, error
"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 @@ type UpstreamPassiveHealthCheckUnhealthy struct {
// 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 @@ func (in *Metadata) DeepCopy() *Metadata {
}
// 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 @@ func (in *Ssl) DeepCopyInto(out *Ssl) {
(*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 @@ metadata:
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 @@ spec:
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 @@ spec:
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 @@ import (
"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 @@ jW4KB95bGOTa7r7DM1Up0MbAIwWoeLBGhOIXk7inurZGg+FNjZMA5Lzm6qo=
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 @@ UnBVSIGJ/c0AhVSDuOAJiF36pvsDysTZXMTFE/9i5bkGOiwtzRNe4Hym/SEZUCpn
8Z1U5/a5LhrDtIKAsCCpRx99P++Eqt2M2YV7jZcTfEbEvxP4XBYcdh30nbq1uEhs
4zEnK1pMx5PnEljN1mcgmL2TPsMVN5DN9zXHW5eNQ6wfXR8rCfHwVIVcUuaB
-----END RSA PRIVATE KEY-----`
- key_compare_update := "HrMHUvE9Esvn7GnZ+vAynaIg/8wlB3r0zm0htmnwofY0a95jf9O5bkBT8pEwjhLvcZOysVlRXE9fYFZ7heHoaihZmZIcnNPPi/SnNr1qVExgIWFYCf6QzpMdv7bMKag8AnYlalvbEIAyJA2tjZ0Gt9aQ9YlzmbGtyFX344481bSfLR/3fpNABO2j/6C6IQxxaGOPRiUeBEJ4VwPxmCUecRPWOHgQfyROReELWwkTIXZ17j0YeABDHWpsHASTjMdupvdwma20TlA3ruNV9WqDn1VE8hDTB4waAImqbZI0bBMdqDFVE0q50DSl2uzzO8X825CLjIa/E0U6JPid41hGOdadZph5Gbpnlou8xwOgRfzG1yyptPCKrAJcgIvsSz/CsYCqaoPCpil4TFjUq4PH0cWo6GlXN95TPX0LrAOh8WMCb7lZYXq5Q2TZ/sn5jF1GIiZZFWVUZujXK2og0I0 [...]
+ keyCompareUpdate := "HrMHUvE9Esvn7GnZ+vAynaIg/8wlB3r0zm0htmnwofY0a95jf9O5bkBT8pEwjhLvcZOysVlRXE9fYFZ7heHoaihZmZIcnNPPi/SnNr1qVExgIWFYCf6QzpMdv7bMKag8AnYlalvbEIAyJA2tjZ0Gt9aQ9YlzmbGtyFX344481bSfLR/3fpNABO2j/6C6IQxxaGOPRiUeBEJ4VwPxmCUecRPWOHgQfyROReELWwkTIXZ17j0YeABDHWpsHASTjMdupvdwma20TlA3ruNV9WqDn1VE8hDTB4waAImqbZI0bBMdqDFVE0q50DSl2uzzO8X825CLjIa/E0U6JPid41hGOdadZph5Gbpnlou8xwOgRfzG1yyptPCKrAJcgIvsSz/CsYCqaoPCpil4TFjUq4PH0cWo6GlXN95TPX0LrAOh8WMCb7lZYXq5Q2TZ/sn5jF1GIiZZFWVUZujXK2og0I042 [...]
// 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 @@ UnBVSIGJ/c0AhVSDuOAJiF36pvsDysTZXMTFE/9i5bkGOiwtzRNe4Hym/SEZUCpn
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 @@ wrw7im4TNSAdwVX4Y1F4svJ2as5SJn5QYGAzXDixNuwzXYrpP9rzA2s=
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 @@ RU+QPRECgYB6XW24EI5+w3STbpnc6VoTS+sy9I9abTJPYo9LpCJwfMYc9Tg9Cx2K
// 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 @@ RU+QPRECgYB6XW24EI5+w3STbpnc6VoTS+sy9I9abTJPYo9LpCJwfMYc9Tg9Cx2K
// 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 @@ RU+QPRECgYB6XW24EI5+w3STbpnc6VoTS+sy9I9abTJPYo9LpCJwfMYc9Tg9Cx2K
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 @@ func (s *Scaffold) ListApisixStreamRoutes() ([]*v1.StreamRoute, error) {
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 @@ package scaffold
import (
"context"
"crypto/tls"
+ "crypto/x509"
"fmt"
"io/ioutil"
"net/http"
@@ -126,6 +127,19 @@ func NewDefaultScaffold() *Scaffold {
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 @@ func (s *Scaffold) NewAPISIXClientWithTCPProxy() *httpexpect.Expect {
})
}
-// 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 @@ func (s *Scaffold) NewAPISIXHttpsClient(host string) *httpexpect.Expect {
})
}
+// 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 @@ data:
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 @@ spec:
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 @@ func (s *Scaffold) NewSecret(name, cert, key string) error {
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 @@ func (s *Scaffold) NewApisixTls(name, host, secretName string) error {
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)