You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by li...@apache.org on 2021/09/16 07:38:27 UTC

[apisix-ingress-controller] branch cert-manager created (now a286292)

This is an automated email from the ASF dual-hosted git repository.

lingsamuel pushed a change to branch cert-manager
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git.


      at a286292  feat: support cert-manager

This branch includes the following new commits:

     new a286292  feat: support cert-manager

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


[apisix-ingress-controller] 01/01: feat: support cert-manager

Posted by li...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

lingsamuel pushed a commit to branch cert-manager
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git

commit a28629290a3d57ff828b9effd9513ffed34e0f12
Author: Ling Samuel <li...@gmail.com>
AuthorDate: Thu Sep 16 08:24:37 2021 +0800

    feat: support cert-manager
---
 docs/en/latest/practices/cert-manager/ca.yaml      |   8 +
 docs/en/latest/practices/cert-manager/issuer.yaml  |   7 +
 .../manage-certificates-with-cert-manager.md       | 237 +++++++++++++++++++++
 ...anage-ingress-certificates-with-cert-manager.md | 191 +++++++++++++++++
 pkg/ingress/controller.go                          |   2 +
 pkg/ingress/secret.go                              |  61 +++---
 pkg/kube/translation/apisix_ssl.go                 |  63 +++++-
 pkg/kube/translation/translator.go                 |   3 +
 test/e2e/ingress/ingress.go                        |  54 +++++
 test/e2e/ingress/secret.go                         | 233 ++++++++++++++++++++
 test/e2e/scaffold/ssl.go                           |  20 ++
 11 files changed, 840 insertions(+), 39 deletions(-)

diff --git a/docs/en/latest/practices/cert-manager/ca.yaml b/docs/en/latest/practices/cert-manager/ca.yaml
new file mode 100644
index 0000000..d751fc7
--- /dev/null
+++ b/docs/en/latest/practices/cert-manager/ca.yaml
@@ -0,0 +1,8 @@
+apiVersion: v1
+kind: Secret
+type: kubernetes.io/tls
+metadata:
+  name: ca-key-pair
+data:
+  tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUY5ekNDQTkrZ0F3SUJBZ0lVRkt1ekFKWmdtL2ZzRlM2SkRyZCtsY3BWWnI4d0RRWUpLb1pJaHZjTkFRRUwKQlFBd2dad3hDekFKQmdOVkJBWVRBa05PTVJFd0R3WURWUVFJREFoYWFHVnFhV0Z1WnpFUk1BOEdBMVVFQnd3SQpTR0Z1WjNwb2IzVXhHREFXQmdOVkJBb01EMEZRU1ZOSldDMVVaWE4wTFVOQlh6RVlNQllHQTFVRUN3d1BRVkJKClUwbFlYME5CWDFKUFQxUmZNUlV3RXdZRFZRUUREQXhCVUVsVFNWZ3VVazlQVkY4eEhEQWFCZ2txaGtpRzl3MEIKQ1FFV0RYUmxjM1JBZEdWemRDNWpiMjB3SGhjTk1qRXdOVEkzTVRNek5qSTRXaGNOTWpJd05USTNNVE16TmpJNApXakNCbkRFT [...]
+  tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRd0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQ1Mwd2dna3BBZ0VBQW9JQ0FRQ3lVZEpVRnZ5QWFrRXgKUHptdEQ0dUM1WnBXRkVocDZyUlRhb21jamhkRDQxY3pYcXFBL1NRejRqTlVFY2d2S1RRbmVTLzBJYlZDRERNZApFWlF5UFE1Q2R3Q3lGbmF6eUdaY0tiM2d5WCtDclZ1M00vbm11T2dPa0VNQWVzWE1sY1ZwNmhBWklubXU2cXhCClBEV3BHVGpJNEpLcERkdi9aSllPbDhnT1JWbjc3TThiYmlCN1kzTXRpcXZHekF0STNaT3dxaEdYdUhMZXJOc0oKWENQTGZMUGFhN3pBNXZJSEYrdXNlQUlmZ3R2MXZndDVVNlRBNEJxa2FrajhnbHU1MmxvSjlMVnBrMG45WXNtOApHRE1uWVF2c [...]
diff --git a/docs/en/latest/practices/cert-manager/issuer.yaml b/docs/en/latest/practices/cert-manager/issuer.yaml
new file mode 100644
index 0000000..f83daa8
--- /dev/null
+++ b/docs/en/latest/practices/cert-manager/issuer.yaml
@@ -0,0 +1,7 @@
+apiVersion: cert-manager.io/v1alpha2
+kind: Issuer
+metadata:
+  name: ca-issuer
+spec:
+  ca:
+    secretName: ca-key-pair
diff --git a/docs/en/latest/practices/manage-certificates-with-cert-manager.md b/docs/en/latest/practices/manage-certificates-with-cert-manager.md
new file mode 100644
index 0000000..37a0322
--- /dev/null
+++ b/docs/en/latest/practices/manage-certificates-with-cert-manager.md
@@ -0,0 +1,237 @@
+---
+title: Manage Certificates With Cert Manager
+---
+
+<!--
+#
+# 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.
+#
+-->
+
+This tutorial will detail how to manage secrets of ApisixTls using cert-manager.
+
+## Prerequisites
+
+* Prepare an available Kubernetes cluster in your workstation, we recommend you to use [Minikube](https://github.com/kubernetes/minikube).
+* Install Apache APISIX in Kubernetes by [Helm Chart](https://github.com/apache/apisix-helm-chart).
+* Install [apisix-ingress-controller](https://github.com/apache/apisix-ingress-controller/blob/master/install.md).
+* Install [cert-manager](https://cert-manager.io/docs/installation/#default-static-install).
+
+
+In this guide, we assume that your APISIX is installed with `ssl` enabled, which is not enabled by default in the Helm Chart. To enable it, you need to set `gateway.tls.enabled=true` during installation.
+
+Assume that the SSL port is `9443`.
+
+## Create Issuer
+
+For testing purposes, we will use a simple CA issuer. All required files can be found [here](./cert-manager).
+
+To create a CA issuer, use the following commands:
+
+```bash
+kubectl apply -f ./cert-manager/ca.yaml
+kubectl apply -f ./cert-manager/issuer.yaml
+```
+
+If the cert-manager is working correctly, we should be able to see the Ready status by running:
+
+```bash
+kubectl get issuer
+```
+
+It should output:
+
+```
+NAME        READY   AGE
+ca-issuer   True    50s
+```
+
+## Create Certificate
+
+Before creating ApisixTls, we should create a `Certificate` resource.
+
+```yaml
+apiVersion: cert-manager.io/v1
+kind: Certificate
+metadata:
+  name: demo-cert
+spec:
+  dnsNames:
+    - local.httpbin.org
+  issuerRef:
+    kind: Issuer
+    name: ca-issuer
+  secretName: example-cert
+  usages:
+    - digital signature
+    - key encipherment
+  renewBefore: 0h55m0s
+  duration: 1h0m0s
+```
+
+Note that we set the parameters `duration` and `renewBefore`. We want to test if the certificate rotation functionality is working well, so a shorter renewal time will help.
+
+Like `Issuer`, we could see its readiness status by running:
+
+```bash
+kubectl get certificate
+```
+
+It should output:
+
+```
+NAME        READY   SECRET        AGE
+demo-cert   True    example-cert  50s
+```
+
+Check the secrets by running:
+
+```bash
+kubectl get secret
+```
+
+It should output:
+
+```
+NAME          TYPE                DATA   AGE
+example-cert  kubernetes.io/tls   3      2m20s
+```
+
+This means that our cert-manager is working properly.
+
+## Create Test Service
+
+We use [kennethreitz/httpbin](https://hub.docker.com/r/kennethreitz/httpbin/) as the service image.
+
+Deploy it by running:
+
+```bash
+kubectl run httpbin --image kennethreitz/httpbin --port 80
+kubectl expose pod httpbin --port 80
+```
+
+## Route the Service
+
+Create an ApisixRoute to route the service:
+
+```yaml
+apiVersion: apisix.apache.org/v2beta1
+kind: ApisixRoute
+metadata:
+  name: httpserver-route
+spec:
+  http:
+    - name: httpbin
+      match:
+        hosts:
+          - local.httpbin.org
+        paths:
+          - "/*"
+      backend:
+        serviceName: httpbin
+        servicePort: 80
+```
+
+Run curl command in a APISIX pod to see if the routing configuration works.
+
+```bash
+kubectl -n <APISIX_NAMESPACE> exec -it <APISIX_POD_NAME> -- curl http://127.0.0.1:9080/ip -H 'Host: local.httpbin.org'
+```
+
+It should output:
+
+```json
+{
+  "origin": "127.0.0.1"
+}
+```
+
+## Secure the Route
+
+Create an ApisixTls to secure the route, referring to the secret created by cert-manager:
+
+```yaml
+apiVersion: apisix.apache.org/v1
+kind: ApisixTls
+metadata:
+  name: example-tls
+spec:
+  hosts:
+    - local.httpbin.org
+  secret:
+    name: example-cert # the secret created by cert-manager
+    namespace: default # secret namespace
+```
+
+Run curl command in a APISIX pod to see if the Ingress and TLS configuration are working.
+
+```bash
+kubectl -n <APISIX_NAMESPACE> exec -it <APISIX_POD_NAME> -- curl --resolve 'local.httpbin.org:9443:127.0.0.1' "https://local.httpbin.org:9443/ip" -k
+```
+
+It should output:
+
+```json
+{
+  "origin": "127.0.0.1"
+}
+```
+
+## Test Certificate Rotation
+
+To verify certificate rotation, we can add a verbose parameter `-v` to curl command:
+
+```bash
+kubectl -n <APISIX_NAMESPACE> exec -it <APISIX_POD_NAME> -- curl --resolve 'local.httpbin.org:9443:127.0.0.1' "https://local.httpbin.org:9443/ip" -k -v
+```
+
+The verbose option will show us the handshake log, which also contains the certificate information.
+
+Example output:
+
+```
+* Added local.httpbin.org:9443:127.0.0.1 to DNS cache
+* Hostname local.httpbin.org was found in DNS cache
+*   Trying 127.0.0.1:9443...
+* Connected to local.httpbin.org (127.0.0.1) port 9443 (#0)
+...
+...
+* Server certificate:
+*  subject: [NONE]
+*  start date: Sep 16 00:14:55 2021 GMT
+*  expire date: Sep 16 01:14:55 2021 GMT
+*  issuer: C=CN; ST=Zhejiang; L=Hangzhou; O=APISIX-Test-CA_; OU=APISIX_CA_ROOT_; CN=APISIX.ROOT_; emailAddress=test@test.com
+```
+
+We could see the start date and expiration date of the server certificate.
+
+Since the `Certificate` we defined requires the cert-manager to renew the cert every 5 minutes, we should be able to see the changes to the server certificate after 5 minutes.
+
+```
+* Added local.httpbin.org:9443:127.0.0.1 to DNS cache
+* Hostname local.httpbin.org was found in DNS cache
+*   Trying 127.0.0.1:9443...
+* Connected to local.httpbin.org (127.0.0.1) port 9443 (#0)
+...
+...
+* Server certificate:
+*  subject: [NONE]
+*  start date: Sep 16 00:19:55 2021 GMT
+*  expire date: Sep 16 01:19:55 2021 GMT
+*  issuer: C=CN; ST=Zhejiang; L=Hangzhou; O=APISIX-Test-CA_; OU=APISIX_CA_ROOT_; CN=APISIX.ROOT_; emailAddress=test@test.com
+```
+
+The certificate was rotated as expected.
diff --git a/docs/en/latest/practices/manage-ingress-certificates-with-cert-manager.md b/docs/en/latest/practices/manage-ingress-certificates-with-cert-manager.md
new file mode 100644
index 0000000..20ed983
--- /dev/null
+++ b/docs/en/latest/practices/manage-ingress-certificates-with-cert-manager.md
@@ -0,0 +1,191 @@
+---
+title: Manage Ingress Certificates With Cert Manager
+---
+
+<!--
+#
+# 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.
+#
+-->
+
+This tutorial will detail how to secure ingress using cert-manager.
+
+## Prerequisites
+
+* Prepare an available Kubernetes cluster in your workstation, we recommend you to use [Minikube](https://github.com/kubernetes/minikube).
+* Install Apache APISIX in Kubernetes by [Helm Chart](https://github.com/apache/apisix-helm-chart).
+* Install [apisix-ingress-controller](https://github.com/apache/apisix-ingress-controller/blob/master/install.md).
+* Install [cert-manager](https://cert-manager.io/docs/installation/#default-static-install).
+
+
+In this guide, we assume that your APISIX is installed with `ssl` enabled, which is not enabled by default in the Helm Chart. To enable it, you need to set `gateway.tls.enabled=true` during installation.
+
+Assume that the SSL port is `9443`.
+
+## Create Issuer
+
+For testing purposes, we will use a simple CA issuer. All required files can be found [here](./cert-manager).
+
+To create a CA issuer, use the following commands:
+
+```bash
+kubectl apply -f ./cert-manager/ca.yaml
+kubectl apply -f ./cert-manager/issuer.yaml
+```
+
+If the cert-manager is working correctly, we should be able to see the Ready status by running:
+
+```bash
+kubectl get issuer
+```
+
+It should output:
+
+```
+NAME        READY   AGE
+ca-issuer   True    50s
+```
+
+## Create Test Certificate
+
+To ensure that cert-manager is working properly, we can create a test `Certificate` resource.
+
+```yaml
+apiVersion: cert-manager.io/v1
+kind: Certificate
+metadata:
+  name: demo-cert
+spec:
+  dnsNames:
+    - example.com
+  issuerRef:
+    kind: Issuer
+    name: ca-issuer
+  secretName: example-cert
+  usages:
+    - digital signature
+    - key encipherment
+```
+
+Like `Issuer`, we could see its readiness status by running:
+
+```bash
+kubectl get certificate
+```
+
+It should output:
+
+```
+NAME        READY   SECRET        AGE
+demo-cert   True    example.com   50s
+```
+
+Check the secrets by running:
+
+```bash
+kubectl get secret
+```
+
+It should output:
+
+```
+NAME          TYPE                DATA   AGE
+example.com   kubernetes.io/tls   3      2m20s
+```
+
+This means that our cert-manager is working properly. 
+
+## Create Test Service
+
+We use [kennethreitz/httpbin](https://hub.docker.com/r/kennethreitz/httpbin/) as the service image.
+
+Deploy it by running:
+
+```bash
+kubectl run httpbin --image kennethreitz/httpbin --port 80
+kubectl expose pod httpbin --port 80
+```
+
+## Secure Ingress
+
+The cert-manager supports several ways to [secure ingress](https://cert-manager.io/docs/usage/ingress/). The easiest way is to use annotations.
+
+By using annotations, we don't need to manage `Certificate` CRD manually.
+
+```yaml
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  name: httpserver-ingress
+  annotations:
+    # add an annotation indicating the issuer to use.
+    cert-manager.io/issuer: "ca-issuer"
+spec:
+  # apisix-ingress-controller is only interested in Ingress
+  # resources with the matched ingressClass name, in our case,
+  # it's apisix.
+  ingressClassName: apisix
+  tls:
+    - hosts:
+        - local.httpbin.org # placing a host in the TLS config will determine what ends up in the cert's subjectAltNames
+      secretName: ingress-cert-manager-tls # cert-manager will store the created certificate in this secret.
+  rules:
+  - host: local.httpbin.org
+    http:
+      paths:
+      - path: /
+        pathType: Prefix
+        backend:
+          service:
+            name: httpbin
+            port:
+              number: 80
+```
+
+The annotation `cert-manager.io/issuer` tells cert-manager which issuer should be used. The Issuer must be in the same namespace as the Ingress resource. Please read [Securing Ingress Resources](https://cert-manager.io/docs/usage/ingress/) for more details.
+
+We should now be able to see the certificate and secret resource created by cert-manager:
+
+```bash
+kubectl get certificate
+kubectl get secret
+```
+
+It should output:
+
+```
+NAME                       READY   SECRET                     AGE
+ingress-cert-manager-tls   True    ingress-cert-manager-tls   2m
+
+NAME                       TYPE                DATA   AGE
+ingress-cert-manager-tls   kubernetes.io/tls   3      3m
+```
+
+## Test
+
+Run curl command in a APISIX pod to see if the Ingress and TLS configuration works.
+
+```bash
+kubectl -n <APISIX_NAMESPACE> exec -it <APISIX_POD_NAME> -- curl --resolve 'local.httpbin.org:9443:127.0.0.1' "https://local.httpbin.org:9443/ip" -k
+```
+
+It should output:
+
+```json
+{
+  "origin": "127.0.0.1"
+}
+```
diff --git a/pkg/ingress/controller.go b/pkg/ingress/controller.go
index 2af856d..0b83a8e 100644
--- a/pkg/ingress/controller.go
+++ b/pkg/ingress/controller.go
@@ -81,6 +81,8 @@ type Controller struct {
 	recorder record.EventRecorder
 	// this map enrolls which ApisixTls objects refer to a Kubernetes
 	// Secret object.
+	// type: Map<SecretKey, Map<ApisixTlsKey, ApisixTls>>
+	// SecretKey is `namespace_name`, ApisixTlsKey is kube style meta key: `namespace/name`
 	secretSSLMap *sync.Map
 
 	// leaderContextCancelFunc will be called when apisix-ingress-controller
diff --git a/pkg/ingress/secret.go b/pkg/ingress/secret.go
index 700bdd7..e0d0333 100644
--- a/pkg/ingress/secret.go
+++ b/pkg/ingress/secret.go
@@ -18,6 +18,7 @@ package ingress
 import (
 	"context"
 	"fmt"
+	v1 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v1"
 	"sync"
 	"time"
 
@@ -28,7 +29,6 @@ import (
 	"k8s.io/client-go/tools/cache"
 	"k8s.io/client-go/util/workqueue"
 
-	"github.com/apache/apisix-ingress-controller/pkg/kube/translation"
 	"github.com/apache/apisix-ingress-controller/pkg/log"
 	"github.com/apache/apisix-ingress-controller/pkg/types"
 	apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
@@ -95,12 +95,10 @@ func (c *secretController) sync(ctx context.Context, ev *types.Event) error {
 		return err
 	}
 	sec, err := c.controller.secretLister.Secrets(namespace).Get(name)
-
-	secretMapkey := namespace + "_" + name
 	if err != nil {
 		if !k8serrors.IsNotFound(err) {
 			log.Errorw("failed to get Secret",
-				zap.String("key", secretMapkey),
+				zap.String("key", key),
 				zap.Error(err),
 			)
 			return err
@@ -108,7 +106,7 @@ func (c *secretController) sync(ctx context.Context, ev *types.Event) error {
 
 		if ev.Type != types.EventDelete {
 			log.Warnw("Secret was deleted before it can be delivered",
-				zap.String("key", secretMapkey),
+				zap.String("key", key),
 			)
 			return nil
 		}
@@ -119,15 +117,16 @@ func (c *secretController) sync(ctx context.Context, ev *types.Event) error {
 			// that means object with same namespace and name was created, discarding
 			// this stale DELETE event.
 			log.Warnw("discard the stale secret delete event since the resource still exists",
-				zap.String("key", secretMapkey),
+				zap.String("key", key),
 			)
 			return nil
 		}
 		sec = ev.Tombstone.(*corev1.Secret)
 	}
+
+	secretMapKey := namespace + "_" + name
 	// sync SSL in APISIX which is store in secretSSLMap
-	// FixMe Need to update the status of CRD ApisixTls
-	ssls, ok := c.controller.secretSSLMap.Load(secretMapkey)
+	ssls, ok := c.controller.secretSSLMap.Load(secretMapKey)
 	if !ok {
 		// This secret is not concerned.
 		return nil
@@ -143,26 +142,25 @@ func (c *secretController) sync(ctx context.Context, ev *types.Event) error {
 		}
 		tls, err := c.controller.apisixTlsLister.ApisixTlses(tlsNamespace).Get(tlsName)
 		if err != nil {
-			log.Warnw("secret related ApisixTls resource not found, skip",
+			log.Debugw("secret related ApisixTls resource not found, skip",
 				zap.String("ApisixTls", tlsMetaKey),
 			)
 			return true
 		}
+
+		// We don't expect a secret to be used as both SSL and mTLS in ApisixTls
 		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",
+			cert, pkey, err := c.controller.translator.ExtractKeyPair(sec, true)
+			if err != nil {
+				log.Errorw("secret required by ApisixTls invalid",
 					zap.String("ApisixTls", tlsMetaKey),
-					zap.Error(translation.ErrEmptyPrivKey),
+					zap.Error(err),
 				)
+				go func(tls *v1.ApisixTls) {
+					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)
+				}(tls)
 				return true
 			}
 			// sync ssl
@@ -170,19 +168,24 @@ func (c *secretController) sync(ctx context.Context, ev *types.Event) error {
 			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),
+			ca, _, err := c.controller.translator.ExtractKeyPair(sec, false)
+			if err != nil {
+				log.Errorw("ca secret required by ApisixTls invalid",
+					zap.String("ApisixTls", tlsMetaKey),
+					zap.Error(err),
 				)
+				go func(tls *v1.ApisixTls) {
+					c.controller.recorderEventS(tls, corev1.EventTypeWarning, _resourceSyncAborted,
+						fmt.Sprintf("sync from ca secret %s changes failed, error: %s", key, err.Error()))
+					c.controller.recordStatus(tls, _resourceSyncAborted, err, metav1.ConditionFalse)
+				}(tls)
 				return true
 			}
 			ssl.Client = &apisixv1.MutualTLSClientConfig{
 				CA: string(ca),
 			}
 		} else {
-			log.Warnw("stale secret cache, ApisixTls doesn't requires target secret",
+			log.Infow("stale secret cache, ApisixTls doesn't requires target secret",
 				zap.String("ApisixTls", tlsMetaKey),
 				zap.String("secret", key),
 			)
@@ -190,7 +193,7 @@ func (c *secretController) sync(ctx context.Context, ev *types.Event) error {
 		}
 		// Use another goroutine to send requests, to avoid
 		// long time lock occupying.
-		go func(ssl *apisixv1.Ssl) {
+		go func(ssl *apisixv1.Ssl, tls *v1.ApisixTls) {
 			err := c.controller.syncSSL(ctx, ssl, ev.Type)
 			if err != nil {
 				log.Errorw("failed to sync ssl to APISIX",
@@ -206,7 +209,7 @@ func (c *secretController) sync(ctx context.Context, ev *types.Event) error {
 					fmt.Sprintf("sync from secret %s changes", key))
 				c.controller.recordStatus(tls, _resourceSynced, nil, metav1.ConditionTrue)
 			}
-		}(ssl)
+		}(ssl, tls)
 		return true
 	})
 	return err
diff --git a/pkg/kube/translation/apisix_ssl.go b/pkg/kube/translation/apisix_ssl.go
index 7ab54fa..bf1bfe9 100644
--- a/pkg/kube/translation/apisix_ssl.go
+++ b/pkg/kube/translation/apisix_ssl.go
@@ -17,12 +17,16 @@ package translation
 import (
 	"errors"
 
+	v1 "k8s.io/api/core/v1"
+
 	"github.com/apache/apisix-ingress-controller/pkg/id"
 	configv1 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v1"
 	apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
 )
 
 var (
+	// ErrUnknownSecretFormat means the secret doesn't contain required fields
+	ErrUnknownSecretFormat = errors.New("unknown secret format")
 	// ErrEmptyCert means the cert field in Kubernetes Secret is not found.
 	ErrEmptyCert = errors.New("missing cert field")
 	// ErrEmptyPrivKey means the key field in Kubernetes Secret is not found.
@@ -34,14 +38,11 @@ func (t *translator) TranslateSSL(tls *configv1.ApisixTls) (*apisixv1.Ssl, error
 	if err != nil {
 		return nil, err
 	}
-	cert, ok := s.Data["cert"]
-	if !ok {
-		return nil, ErrEmptyCert
-	}
-	key, ok := s.Data["key"]
-	if !ok {
-		return nil, ErrEmptyPrivKey
+	cert, key, err := t.ExtractKeyPair(s, true)
+	if err != nil {
+		return nil, err
 	}
+
 	var snis []string
 	for _, host := range tls.Spec.Hosts {
 		snis = append(snis, string(host))
@@ -61,9 +62,9 @@ func (t *translator) TranslateSSL(tls *configv1.ApisixTls) (*apisixv1.Ssl, error
 		if err != nil {
 			return nil, err
 		}
-		ca, ok := caSecret.Data["cert"]
-		if !ok {
-			return nil, ErrEmptyCert
+		ca, _, err := t.ExtractKeyPair(caSecret, false)
+		if err != nil {
+			return nil, err
 		}
 		ssl.Client = &apisixv1.MutualTLSClientConfig{
 			CA:    string(ca),
@@ -73,3 +74,45 @@ func (t *translator) TranslateSSL(tls *configv1.ApisixTls) (*apisixv1.Ssl, error
 
 	return ssl, nil
 }
+
+func (t *translator) ExtractKeyPair(s *v1.Secret, hasPrivateKey bool) ([]byte, []byte, error) {
+	if _, ok := s.Data["cert"]; ok {
+		return t.extractApisixSecretKeyPair(s, hasPrivateKey)
+	} else if _, ok := s.Data[v1.TLSCertKey]; ok {
+		return t.extractKubeSecretKeyPair(s, hasPrivateKey)
+	} else {
+		return nil, nil, ErrUnknownSecretFormat
+	}
+}
+
+func (t *translator) extractApisixSecretKeyPair(s *v1.Secret, hasPrivateKey bool) (cert []byte, key []byte, err error) {
+	var ok bool
+	cert, ok = s.Data["cert"]
+	if !ok {
+		return nil, nil, ErrEmptyCert
+	}
+
+	if hasPrivateKey {
+		key, ok = s.Data["key"]
+		if !ok {
+			return nil, nil, ErrEmptyPrivKey
+		}
+	}
+	return
+}
+
+func (t *translator) extractKubeSecretKeyPair(s *v1.Secret, hasPrivateKey bool) (cert []byte, key []byte, err error) {
+	var ok bool
+	cert, ok = s.Data[v1.TLSCertKey]
+	if !ok {
+		return nil, nil, ErrEmptyCert
+	}
+
+	if hasPrivateKey {
+		key, ok = s.Data[v1.TLSPrivateKeyKey]
+		if !ok {
+			return nil, nil, ErrEmptyPrivKey
+		}
+	}
+	return
+}
diff --git a/pkg/kube/translation/translator.go b/pkg/kube/translation/translator.go
index c1f6ac9..4d0857d 100644
--- a/pkg/kube/translation/translator.go
+++ b/pkg/kube/translation/translator.go
@@ -88,6 +88,9 @@ type Translator interface {
 	// TranslateApisixConsumer translates the configv2alpha1.APisixConsumer object into the APISIX Consumer
 	// resource.
 	TranslateApisixConsumer(*configv2alpha1.ApisixConsumer) (*apisixv1.Consumer, error)
+	// ExtractKeyPair extracts certificate and private key pair from secret
+	// Supports APISIX style ("cert" and "key") and Kube style ("tls.crt" and "tls.key)
+	ExtractKeyPair(s *corev1.Secret, hasPrivateKey bool) ([]byte, []byte, error)
 }
 
 // TranslatorOptions contains options to help Translator
diff --git a/test/e2e/ingress/ingress.go b/test/e2e/ingress/ingress.go
index 7d3d45b..8905730 100644
--- a/test/e2e/ingress/ingress.go
+++ b/test/e2e/ingress/ingress.go
@@ -261,6 +261,60 @@ spec:
 		s.NewAPISIXHttpsClientWithCertificates(host, true, caCertPool, []tls.Certificate{}).
 			GET("/ip").WithHeader("Host", host).Expect().Status(http.StatusOK)
 	})
+
+	ginkgo.It("should support ingress v1 with kube style tls secret", func() {
+		// create secrets
+		err := s.NewKubeTlsSecret(serverCertSecret, serverCert, serverKey)
+		assert.Nil(ginkgo.GinkgoT(), err, "create server cert secret error")
+
+		// create ingress
+		host := "mtls.httpbin.local"
+		// create route
+		backendSvc, backendSvcPort := s.DefaultHTTPBackend()
+		ing := fmt.Sprintf(`
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  name: httpbin-ingress-https
+  annotations:
+    kubernetes.io/ingress.class: apisix
+spec:
+  tls:
+  - hosts:
+    - %s
+    secretName: %s
+  rules:
+  - host: %s
+    http:
+      paths:
+      - path: /
+        pathType: Prefix
+        backend:
+          service:
+            name: %s
+            port:
+              number: %d
+`, host, serverCertSecret, host, backendSvc, backendSvcPort[0])
+		assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ing))
+		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")
+
+		apisixSsls, err := s.ListApisixSsl()
+		assert.Nil(ginkgo.GinkgoT(), err, "list SSLs error")
+		assert.Len(ginkgo.GinkgoT(), apisixSsls, 1, "SSL number should be 1")
+		assert.Equal(ginkgo.GinkgoT(), id.GenID(s.Namespace()+"_httpbin-ingress-https-tls"), apisixSsls[0].ID, "SSL name")
+		assert.Equal(ginkgo.GinkgoT(), apisixSsls[0].Snis, []string{host}, "SSL configuration")
+
+		caCertPool := x509.NewCertPool()
+		ok := caCertPool.AppendCertsFromPEM([]byte(rootCA))
+		assert.True(ginkgo.GinkgoT(), ok, "Append cert to CA pool")
+
+		s.NewAPISIXHttpsClientWithCertificates(host, true, caCertPool, []tls.Certificate{}).
+			GET("/ip").WithHeader("Host", host).Expect().Status(http.StatusOK)
+	})
 })
 
 var _ = ginkgo.Describe("support ingress.networking/v1", func() {
diff --git a/test/e2e/ingress/secret.go b/test/e2e/ingress/secret.go
index 421a6a2..8717107 100644
--- a/test/e2e/ingress/secret.go
+++ b/test/e2e/ingress/secret.go
@@ -269,4 +269,237 @@ UnBVSIGJ/c0AhVSDuOAJiF36pvsDysTZXMTFE/9i5bkGOiwtzRNe4Hym/SEZUCpn
 		assert.Nil(ginkgo.GinkgoT(), err, "list tls error")
 		assert.Len(ginkgo.GinkgoT(), tls, 0, "tls number not expect")
 	})
+
+	ginkgo.It("create a kube style SSL and then update secret ", func() {
+		backendSvc, backendSvcPort := s.DefaultHTTPBackend()
+		apisixRoute := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2alpha1
+kind: ApisixRoute
+metadata:
+  name: httpbin-route
+spec:
+  http:
+  - name: rule1
+    match:
+      hosts:
+      - api6.com
+      paths:
+      - /ip
+    backend:
+      serviceName: %s
+      servicePort: %d
+`, backendSvc, backendSvcPort[0])
+		assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(apisixRoute))
+
+		secretName := "test-apisix-tls"
+		cert := `-----BEGIN CERTIFICATE-----
+MIIFcjCCA1qgAwIBAgIJALDqPppBVXQ3MA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNV
+BAYTAkNOMRAwDgYDVQQIDAdKaWFuZ3N1MQ8wDQYDVQQHDAZTdXpob3UxEDAOBgNV
+BAoMB2FwaTcuYWkxEDAOBgNVBAsMB2FwaTcuYWkxDzANBgNVBAMMBmp3LmNvbTAg
+Fw0yMTA0MDkwNzEyMDBaGA8yMDUxMDQwMjA3MTIwMFowZTELMAkGA1UEBhMCQ04x
+EDAOBgNVBAgMB0ppYW5nc3UxDzANBgNVBAcMBlN1emhvdTEQMA4GA1UECgwHYXBp
+Ny5haTEQMA4GA1UECwwHYXBpNy5haTEPMA0GA1UEAwwGancuY29tMIICIjANBgkq
+hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuEPPnUMlSw41CTdxUNxkQ4gAZ7cPotwY
+Y6sVLGtWoR8gKFSZImQIor3UF+8HhN/ZOFRv5tSeeQ/MTE72cs2T5mp6eRU8OrSV
+0npk/TpZfaCx7zobsfXB4YU1NZcVWKthFF5X8p//l5gxUMU8V4a01P0aDTmZ67wG
+3Fhr0AC067GVYvdwp1yRt6TUUk8ha7JsiySchUIFhX5QMWmrSNhc1bDnHetejMFl
+itFFPZkeYG89O/7Ca1K3ca/VVu+/IJ4h7fbF3rt4uP182cJdHl1L94dQSKCJukaW
+v+xauWnm4hxOzBK7ImpYB/2eP2D33tmuCLeSv4S+bTG1l7hIN9C/xrYPzfun415h
+M2jMK69aAkQL71xa+66kVxioJyNYogYz3ss5ruzDL8K/7bkdO0Zzqldd2+j8lkTl
+X4csA+aMHF3v/U7hL/4Wdwi8ziwToRMq9KK9vuh+mPgcdtFGFml+sU+NQfJNm/BN
+7fRMZKDIHLacSPE0GUkfW+x3dXOP2lWSZe/iOBZ0NOGNdrOnxTRTr7IH7DYU8aXF
+w2GqfAFEQbD4wazCh1AI8lkZr6mPGB1q+HnF2IF7kkgXBHtI5U2KErgcX5BirIVe
+v0Yg/OxbbymeTh/hNCcY1kJ1YUCbm9U3U6ZV+d8lj7dQHtugcAzWxSTwpBLVUPrO
+eFuhSMLVblUCAwEAAaMjMCEwHwYDVR0RBBgwFoIIYXBpNi5jb22CCiouYXBpNi5j
+b20wDQYJKoZIhvcNAQELBQADggIBAFgeSuMPpriYUrQByO3taiY1+56s+KoAmsyA
+LH15n2+thAgorusX2g1Zd7UNBHBtVO8c+ihpQb+2PnmgTTGT70ndpRbV5F6124Mt
+Hui/X0kjm76RYd1QKN1VFp0Zo+JVdRa+VhDsXWjO0VetINmFhNINFEJctyeHB8oi
+aaDL0wZrevHh47hBqtnrmLl+QVG34aLBRhZ5953leiNvXHUJNaT0nLgf0j9p4esS
+b2bx9uP4pFI1T9wcv/TE3K0rQbu/uqGY6MgznXHyi4qIK/I+WCa3fF2UZ5P/5EUM
+k2ptQneYkLLUVwwmj8C04bYhYe7Z6jkYYp17ojxIP+ejOY1eiE8SYKNjKinmphrM
+5aceqIyfbE4TPqvicNzIggA4yEMPbTA0PC/wqbCf61oMc15hwacdeIaQGiwsM+pf
+DTk+GBxp3megx/+0XwTQbguleTlHnaaES+ma0vbl6a1rUK0YAUDUrcfFLv6EFtGD
+6EHxFf7gH9sTfc2RiGhKxUhRbyEree+6INAvXy+AymVYmQmKuAFqkDQJ+09bTfm8
+bDs+00FijzHFBvC8cIhNffj0qqiv35g+9FTwnE9qpunlrtKG/sMgEXX6m8kL1YQ8
+m5DPGhyEZyt5Js2kzzo8TyINPKmrqriYuiD4p4EH13eSRs3ayanQh6ckC7lb+WXq
+3IrSc5hO
+-----END CERTIFICATE-----`
+		key := `-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----`
+		// key compare
+		keyCompare := "HrMHUvE9Esvn7GnZ+vAynaIg/8wlB3r0zm0htmnwofaOw61M98WSdvoWLaQa8YKSdemgQUz2W4MYk2rRZcVSzHfJOLRG7g4ieZau6peDYOmPmp/0ZZFpOzBKoWHN3QP/8i/7SF+JX+EDLD2JO2+GM6iR3f2Zj7v0vx+CcoQ1rjxaXNETSSHo8yvW6pdFZOLgJk4rOHKGypnqzygxxamM8Hq7WSPrWhNe47y1QAfz42kBQXRUJpNNd7W749cTsMWCqBlR+8klTlnSFHkjyijBZjg5ihqZsi/8JzHGrmAixZ54ugPgbufD0/ZJdo3w7opJc4WTnUI2GhiBL+ENCA0X1s/6H8JG8zsC50PvxOBpRgK455TTvejm1JHyt0GTh7c4WFEeQSrbEFzS89BpVrPtre2enO38pkILI8ty8r6tIbZzuOJhM6ZpxQQcAe8OUvFuIIlx21yBvlljbu3eH5Hg7X+wtJR [...]
+		// create kube secret
+		err := s.NewKubeTlsSecret(secretName, cert, key)
+		assert.Nil(ginkgo.GinkgoT(), err, "create secret error")
+		// create ApisixTls resource
+		tlsName := "tls-name"
+		host := "api6.com"
+		err = s.NewApisixTls(tlsName, host, secretName)
+		assert.Nil(ginkgo.GinkgoT(), err, "create tls error")
+		// check ssl in APISIX
+		time.Sleep(10 * time.Second)
+		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")
+		assert.Equal(ginkgo.GinkgoT(), keyCompare, tls[0].Key, "tls key not expect")
+
+		// check DP
+		s.NewAPISIXHttpsClient(host).GET("/ip").WithHeader("Host", host).Expect().Status(http.StatusOK).Body().Raw()
+
+		certUpdate := `-----BEGIN CERTIFICATE-----
+MIIFcjCCA1qgAwIBAgIJAM7zkxmhGdNEMA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNV
+BAYTAkNOMRAwDgYDVQQIDAdKaWFuZ3N1MQ8wDQYDVQQHDAZTdXpob3UxEDAOBgNV
+BAoMB2FwaTcuYWkxEDAOBgNVBAsMB2FwaTcuYWkxDzANBgNVBAMMBmp3LmNvbTAg
+Fw0yMTA0MDkwNzE1MzNaGA8yMDUxMDQwMjA3MTUzM1owZTELMAkGA1UEBhMCQ04x
+EDAOBgNVBAgMB0ppYW5nc3UxDzANBgNVBAcMBlN1emhvdTEQMA4GA1UECgwHYXBp
+Ny5haTEQMA4GA1UECwwHYXBpNy5haTEPMA0GA1UEAwwGancuY29tMIICIjANBgkq
+hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlXGEL4P5+mjllbb4VxkpqjsHny2wY7QZ
+NbA0INfLdBq3B5avL0Gp/nHk1iOMq+RkUu+DDb9i0wKw0j1ygaaN4WNXCa7/LZ7E
+IVDKcCKF7JQUi5za9A36nTBMLb7ZbFwxnxGkCK2AwdIbUIB6Ciq/d+WEbZelspPQ
+PtG9e8pVYTbNhRublGgVD+LEUHYdvSneveFQxdmHhNQMPXNbh+Qmgz48n7cLMEHh
+NwRyrWOb3zhdDeKlTm/yfPAQwvQNet+Ol3g19tKhS7WzDndrPfPNYAsoOdXZ5TTY
+AlXzwsbVYSOW4gMW39p68ZnoseQHBVZkiTfN2CGvWbzd9gUXOiQship+xRfSoVEY
+5FN1KsTIA5fdDbgqCTM4Ed1FTq39hJSBnsQObzQtfjgz0Sivwp9q0NoXX+YM6eBJ
+1kzVx6r70UMq+NJbs7mvAW9Ehzv2m2GsinMq3lyvVwSbsJWX9vOHKxmuWympmOw1
+7sUcH4DNHCxnzLK0L0oDV2atpNyKFvmMWSSmw09wOvuE+G+CLwILvgiSiTRPe1Gm
+cgaTC92nZsafA5d8r2uxxJRHD7PAQSE5YnsdLsXAFSegYyHss2jq8lzVFwWhTk4t
+A/8si8qUHh/hHKt71dYrSRmYTTyj834P8KDwQAMYBGcThtZa7PudOR22toohyte7
+Y7j4V6li2mkCAwEAAaMjMCEwHwYDVR0RBBgwFoIIYXBpNi5jb22CCiouYXBpNi5j
+b20wDQYJKoZIhvcNAQELBQADggIBAGmueLf8FFxVCgKKPQbHgPJ8/bTBQfBXi4cG
+eg46QT3j4r8cw05CM64NXQs51q8kWa4aIhGzXweZPZZcnLppIZX31HguAsaOTWEv
+2T3v9nqacMdujdyB+ll/bkwBoVudF47/JqQvHkevE0W43EZGgtmaPWki6psXK0C7
+Fwx95FeUizYrbsDDKbPKQ2iv70oES2bhhKi6K3o6uV6cFuUTxoNsjv9mjMM93xL/
+r5HhOAg7/voXwkvYAkOoehGRF4pwsfmjCJMF9k3BqDOZM+8087EHZFiWCPOvseA/
+FNxtEiDeUt86BGO9wzTv4ZN0gkJOrxATIw15wRemxnXJ8GUweiZh+U2nvDSklZq4
+Z4Wj2tWfa/yIqRBOZyqcAOS6jCnz/pYEGRniO6DMZSN1VX8A5pH8HNMnREW8Sze+
+c9cNZwquESqGFfKMAHOzuyxJhqEvuwwT/JNCLUgtQICPtdAQmNJQEwDAIdmS8VrX
+XNsBIYSloIopKd7IY3V7Y8yASs7jKLrtJN4AE+SpssWuTa4p2LSO08sW3NP8r86G
+1cs5R6Mckmqaqk5ES9gRbKmhm4goamb2fe2HJ/PTFyOrWtucA6OU3AdrQNXY+qbK
+FskpOMlyl8WZo6JMsbOjd+tVygf9QuhvH9MxYDvppfeHxS0B7mYGZDOV/4G2vn2A
+C1K1xkvo
+-----END CERTIFICATE-----`
+		keyUpdate := `-----BEGIN RSA PRIVATE KEY-----
+MIIJKQIBAAKCAgEAlXGEL4P5+mjllbb4VxkpqjsHny2wY7QZNbA0INfLdBq3B5av
+L0Gp/nHk1iOMq+RkUu+DDb9i0wKw0j1ygaaN4WNXCa7/LZ7EIVDKcCKF7JQUi5za
+9A36nTBMLb7ZbFwxnxGkCK2AwdIbUIB6Ciq/d+WEbZelspPQPtG9e8pVYTbNhRub
+lGgVD+LEUHYdvSneveFQxdmHhNQMPXNbh+Qmgz48n7cLMEHhNwRyrWOb3zhdDeKl
+Tm/yfPAQwvQNet+Ol3g19tKhS7WzDndrPfPNYAsoOdXZ5TTYAlXzwsbVYSOW4gMW
+39p68ZnoseQHBVZkiTfN2CGvWbzd9gUXOiQship+xRfSoVEY5FN1KsTIA5fdDbgq
+CTM4Ed1FTq39hJSBnsQObzQtfjgz0Sivwp9q0NoXX+YM6eBJ1kzVx6r70UMq+NJb
+s7mvAW9Ehzv2m2GsinMq3lyvVwSbsJWX9vOHKxmuWympmOw17sUcH4DNHCxnzLK0
+L0oDV2atpNyKFvmMWSSmw09wOvuE+G+CLwILvgiSiTRPe1GmcgaTC92nZsafA5d8
+r2uxxJRHD7PAQSE5YnsdLsXAFSegYyHss2jq8lzVFwWhTk4tA/8si8qUHh/hHKt7
+1dYrSRmYTTyj834P8KDwQAMYBGcThtZa7PudOR22toohyte7Y7j4V6li2mkCAwEA
+AQKCAgEAipLCQdUdHWfbS6JoUUtR6TnnbWDOaQV9Qt1q2PGBBN4oze6Z7zXyTbCK
+w04fiNy1cnoJidvn5UZfc/Pqk/I/KboV4TLWCBVqRIJH1QcOLDt7eaIvHZNAyjUY
+zmpj7ijnElsnQayw8gjDrzgS8g6FkMXQsFaoHRkXRsjx7THHTeelV0IsV8bTkPFz
+nDCKjveeRXACmBOHqFGAMBMh0rZqR9SUHn168HqGPZ/wPntY8/mtv8xkAIGp1tQ8
+lqn7Pe7CqA2d7IuPaUbJDCcL2FyUGfT+jfKQpAsGKdRNvlTlU7fOlzDKKzTKP/G5
+ZgrNv9NGUj7erwU9Nfb90r0RtqYIac2CQnBDNx6snsSLx+QlO5wEQ8+xZ3zjKkGi
+VazSaSqpkK2MV7KwyxJD5WEMqyHuUufQGU8TVywi4RkH21VXiwxwCbRLpF/gdBMi
+2015JF1qd/t4GSHc6CtxqKslSxvNpr2cG6c/5Tx2gC3X6ARMr01YDFdKKI/FrsTQ
+w3/E/WNKoPNgAXy361oaT5QVGOyBbL8h9mmDW6vD3R0aLLzdZKsQU+DNs6tcAl10
+mboKMBDVyg2Qew5QsumD89EpA3Da9VkyMxcPmOJ7oOJqscNF3Ejyx8ePlF+jS3dR
+467uaXERVCGep2WDuYcv5Y14uUYAVj+P9aH85YjozA2KzG0aiqECggEBAMZvZwwd
+fgub7RrU/0sHWw50JupVaRIjVIgopTmUw6MWDcpjn6BiCsMCJlabHxeN8XJYn3+E
+aro8TZMvMYvjQQ0yrdKbPw8VLoTNfu++Bn/HPQ8gHFV+wzk33xfKRlRqxXic+MlG
+SQ33IV+xr7JoW4fiMjSvOpp3kJj459mSOBLjhcXW7N4sMPhY6O72g2pqa8ncmOJT
+ZU94VlssAxL3B1P1wsH8HsjhIluDHT+9qwsHhq/prIGLs4ydQSNB0m1oDT21zK/R
+jC7fDbTT4oTZzy8QYiLDwW4ugct53HMQCnJfOdX4F6aOxt7k4d8VwoGuliKyjHii
+VX4C+LZfT/64XiUCggEBAMDLyfqY8PK4ULNPrpSh92IOX5iwCcwGmOYDwGIeIbO6
+S1Pp7WGhA6Ryap/+F5gIdhB0qfnhAslMrQNRFfo3EcHF86UiCsfIRbPh7fwC2s0F
+4G+tST6TsPyCddjWiQsvsmT1eYPNDgPj6JDl3d0mzboVsVge46RORTAhm4k2pobZ
+EUDl+bljWM4LTf+pjN4DTRptwwueSqLVfxdkMhrHSG/NAYFerUKozxJGZeavnUcL
+c+WUCtPJvyQrx2CBhn8LQ7xsPJJPYjNwSf6joMNXPyGvfPYQaxaK1NM0y/7KfuYN
+N8DoBwwnpZIvcznjHDWY0P/cIZFKC2mmq6N062z/bfUCggEAey2oQAMGvVobgy55
+EzALvBsqFQjT4miADs18UxQfpVsJUHsrGboCiC8LcXN1h3+bQ6nzyIqAXf8VAKqp
+DPcS6IhvEm9AY7J4YAPYKiZBjow1QPBj5kZ8FUaze+caZUiqMEbwwLCapMqlsutv
+70WMm/szwzSLIlvaLLtF4O89U6xc3ASgoQG5nFBEuCHaTfKl2nbPiJ7QItbGdG4L
+sngZ2mqSbSx+R6BJXZk0TN8GECCp4QUjCn+YA0+Sobo4T6Xpokb6OqHPbUEVFwz4
+bhNu4v4+jOoLZsQD2jVZPSvV8E1gb4xD0iaLGM3n0D2HskyX8g332OKcQ07A6SSd
+WbdE6QKCAQEAiC2pzgNHdfowrmcjBkNdLHrAlWYKlX03dIjD08o6vethl7UNAj+s
+BfT3UWk1myKm2jq9cQ25XRx2vHgC0Qki1r8OuN5RxQm2CjgUVERj7hsvi1JYAQZr
+JgC0YuQuSqN3G460NR+ava62r9pdmv70o3L9ICQ5YO4UOsoSRZo/h9I9OJz4hjUh
+HfCoOGS3Zn3ocTmEYml9iITKz2fraDTI+odQf+Oy9/mqwdrN0WLL8cmqJEgsWaoQ
+A+mUW5tBt+zp/GZrZmECGRlAesdzH2c55X5CAsBYE8UeTMznJmI7vh0p+20oxTIf
+5iDz/7hmTYlSXtdLMoedhhO++qb0P7owHQKCAQBbjTZSMRiqevr3JlEouxioZ+XD
+yol9hMGQEquLWzqWlvP/LvgNT0PNRcE/qrMlPo7kzoq0r8PbL22tBl2C7C0e+BIv
+UnBVSIGJ/c0AhVSDuOAJiF36pvsDysTZXMTFE/9i5bkGOiwtzRNe4Hym/SEZUCpn
+4hL3iPXCYEWz5hDeoucgdZQjHv2JeQQ3NR9OokwQOwvzlx8uYwgV/6ZT9dJJ3AZL
+8Z1U5/a5LhrDtIKAsCCpRx99P++Eqt2M2YV7jZcTfEbEvxP4XBYcdh30nbq1uEhs
+4zEnK1pMx5PnEljN1mcgmL2TPsMVN5DN9zXHW5eNQ6wfXR8rCfHwVIVcUuaB
+-----END RSA PRIVATE KEY-----`
+		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.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(), keyCompareUpdate, tlsUpdate[0].Key, "tls key not expect")
+		// check DP
+		s.NewAPISIXHttpsClient(host).GET("/ip").WithHeader("Host", host).Expect().Status(http.StatusOK).Body().Raw()
+
+		// delete ApisixTls
+		err = s.DeleteApisixTls(tlsName, host, secretName)
+		assert.Nil(ginkgo.GinkgoT(), err, "delete tls error")
+		// check ssl in APISIX
+		time.Sleep(10 * time.Second)
+		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/scaffold/ssl.go b/test/e2e/scaffold/ssl.go
index e96fa45..65531cd 100644
--- a/test/e2e/scaffold/ssl.go
+++ b/test/e2e/scaffold/ssl.go
@@ -32,6 +32,15 @@ data:
   cert: %s
   key: %s
 `
+	_kubeTlsSecretTemplate = `
+apiVersion: v1
+kind: Secret
+metadata:
+  name: %s
+data:
+  tls.crt: %s
+  tls.key: %s
+`
 	_clientCASecretTemplate = `
 apiVersion: v1
 kind: Secret
@@ -82,6 +91,17 @@ func (s *Scaffold) NewSecret(name, cert, key string) error {
 	return nil
 }
 
+// NewKubeTlsSecret new a kube style tls secret
+func (s *Scaffold) NewKubeTlsSecret(name, cert, key string) error {
+	certBase64 := base64.StdEncoding.EncodeToString([]byte(cert))
+	keyBase64 := base64.StdEncoding.EncodeToString([]byte(key))
+	secret := fmt.Sprintf(_kubeTlsSecretTemplate, name, certBase64, keyBase64)
+	if err := k8s.KubectlApplyFromStringE(s.t, s.kubectlOptions, secret); err != nil {
+		return err
+	}
+	return nil
+}
+
 // NewClientCASecret new a k8s secret
 func (s *Scaffold) NewClientCASecret(name, cert, key string) error {
 	certBase64 := base64.StdEncoding.EncodeToString([]byte(cert))