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:28 UTC

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

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-----
+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
+-----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))