Skip to content

Commit

Permalink
e2e: tls for mysql client (#2227)
Browse files Browse the repository at this point in the history
* e2e: tls for mysql client

* fix CI

* increase ephemeral-storage to 60Gi

* sleep a minute to wait cert-manger webhook
  • Loading branch information
weekface authored Apr 21, 2020
1 parent 5341bab commit a6bbe4c
Show file tree
Hide file tree
Showing 6 changed files with 349 additions and 39 deletions.
4 changes: 2 additions & 2 deletions ci/pingcap_tidb_operator_build_kind.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ spec:
requests:
memory: "8000Mi"
cpu: 8000m
ephemeral-storage: "50Gi"
ephemeral-storage: "60Gi"
limits:
memory: "8000Mi"
cpu: 8000m
ephemeral-storage: "50Gi"
ephemeral-storage: "60Gi"
# kind needs /lib/modules and cgroups from the host
volumeMounts:
- mountPath: /lib/modules
Expand Down
76 changes: 74 additions & 2 deletions tests/e2e/tidbcluster/tidbcluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ var _ = ginkgo.Describe("[tidb-operator] TiDBCluster", func() {
backupFolder := time.Now().Format(time.RFC3339)

// create backup cluster
tcFrom := fixture.GetTidbCluster(ns, tcNameFrom, utilimage.TiDBBRVersion)
tcFrom := fixture.GetTidbCluster(ns, tcNameFrom, utilimage.TiDBV4Version)
tcFrom.Spec.PD.Replicas = 1
tcFrom.Spec.TiKV.Replicas = 1
tcFrom.Spec.TiDB.Replicas = 1
Expand All @@ -314,7 +314,7 @@ var _ = ginkgo.Describe("[tidb-operator] TiDBCluster", func() {
clusterFrom := newTidbClusterConfig(e2econfig.TestConfig, ns, tcNameFrom, "", "")

// create restore cluster
tcTo := fixture.GetTidbCluster(ns, tcNameTo, utilimage.TiDBBRVersion)
tcTo := fixture.GetTidbCluster(ns, tcNameTo, utilimage.TiDBV4Version)
tcTo.Spec.PD.Replicas = 1
tcTo.Spec.TiKV.Replicas = 1
tcTo.Spec.TiDB.Replicas = 1
Expand Down Expand Up @@ -1139,6 +1139,78 @@ var _ = ginkgo.Describe("[tidb-operator] TiDBCluster", func() {
})
framework.ExpectNoError(err, "not clear TiDB failureMembers when scale TiDB to zero")
})

ginkgo.Context("[Feature: TLS]", func() {
ginkgo.BeforeEach(func() {
ginkgo.By("Installing cert-manager")
err := installCertManager(f.ClientSet)
framework.ExpectNoError(err, "failed to install cert-manager")
})

ginkgo.AfterEach(func() {
ginkgo.By("Deleting cert-manager")
err := deleteCertManager(f.ClientSet)
framework.ExpectNoError(err, "failed to delete cert-manager")
})

ginkgo.It("TLS for MySQL Client", func() {
tcName := "tls"

ginkgo.By("Installing tidb issuer")
err := installTiDBIssuer(ns, tcName)
framework.ExpectNoError(err, "failed to generate tidb issuer template")

ginkgo.By("Installing tidb server and client certificate")
err = installTiDBCertificates(ns, tcName)
framework.ExpectNoError(err, "failed to install tidb server and client certificate template")

ginkgo.By("Creating tidb cluster")
tc := fixture.GetTidbCluster(ns, tcName, utilimage.TiDBV4Version)
tc.Spec.PD.Replicas = 3
tc.Spec.TiKV.Replicas = 3
tc.Spec.TiDB.Replicas = 2
tc.Spec.TiDB.TLSClient = &v1alpha1.TiDBTLSClient{Enabled: true}
err = genericCli.Create(context.TODO(), tc)
framework.ExpectNoError(err)
err = oa.WaitForTidbClusterReady(tc, 30*time.Minute, 15*time.Second)
framework.ExpectNoError(err)

ginkgo.By("Connecting to tidb server to verify the connection is TLS enabled")
err = wait.PollImmediate(time.Second*5, time.Minute*5, tidbIsTLSEnabled(fw, c, ns, tcName, ""))
framework.ExpectNoError(err)

ginkgo.By("Scaling out tidb cluster")
err = controller.GuaranteedUpdate(genericCli, tc, func() error {
tc.Spec.PD.Replicas = 5
tc.Spec.TiKV.Replicas = 5
tc.Spec.TiDB.Replicas = 3
return nil
})
framework.ExpectNoError(err)
err = oa.WaitForTidbClusterReady(tc, 30*time.Minute, 15*time.Second)
framework.ExpectNoError(err)

ginkgo.By("Scaling in tidb cluster")
err = controller.GuaranteedUpdate(genericCli, tc, func() error {
tc.Spec.PD.Replicas = 3
tc.Spec.TiKV.Replicas = 3
tc.Spec.TiDB.Replicas = 2
return nil
})
framework.ExpectNoError(err)
err = oa.WaitForTidbClusterReady(tc, 30*time.Minute, 15*time.Second)
framework.ExpectNoError(err)

ginkgo.By("Upgrading tidb cluster")
err = controller.GuaranteedUpdate(genericCli, tc, func() error {
tc.Spec.Version = "nightly"
return nil
})
framework.ExpectNoError(err)
err = oa.WaitForTidbClusterReady(tc, 30*time.Minute, 15*time.Second)
framework.ExpectNoError(err)
})
})
})

func newTidbClusterConfig(cfg *tests.Config, ns, clusterName, password, tidbVersion string) tests.TidbClusterConfig {
Expand Down
271 changes: 271 additions & 0 deletions tests/e2e/tidbcluster/tls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
// Copyright 2020 PingCAP, Inc.
//
// Licensed 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,
// See the License for the specific language governing permissions and
// limitations under the License.

package tidbcluster

import (
"bytes"
"crypto/tls"
"crypto/x509"
"database/sql"
"fmt"
"io/ioutil"
"os"
"os/exec"
"text/template"
"time"

"github.com/go-sql-driver/mysql"
"github.com/pingcap/tidb-operator/pkg/controller"
"github.com/pingcap/tidb-operator/pkg/util"
"github.com/pingcap/tidb-operator/tests/e2e/util/portforward"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/kubernetes/test/e2e/framework"
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
)

var tidbIssuerTmpl = `
apiVersion: cert-manager.io/v1alpha2
kind: Issuer
metadata:
name: {{ .ClusterName }}-selfsigned-ca-issuer
namespace: {{ .Namespace }}
spec:
selfSigned: {}
---
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
name: {{ .ClusterName }}-ca
namespace: {{ .Namespace }}
spec:
secretName: {{ .ClusterName }}-ca-secret
commonName: "TiDB CA"
isCA: true
issuerRef:
name: {{ .ClusterName }}-selfsigned-ca-issuer
kind: Issuer
---
apiVersion: cert-manager.io/v1alpha2
kind: Issuer
metadata:
name: {{ .ClusterName }}-tidb-issuer
namespace: {{ .Namespace }}
spec:
ca:
secretName: {{ .ClusterName }}-ca-secret
`

var tidbCertificatesTmpl = `
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
name: {{ .ClusterName }}-tidb-server-secret
namespace: {{ .Namespace }}
spec:
secretName: {{ .ClusterName }}-tidb-server-secret
duration: 8760h # 365d
renewBefore: 360h # 15d
organization:
- PingCAP
commonName: "TiDB Server"
usages:
- server auth
dnsNames:
- "{{ .ClusterName }}-tidb"
- "{{ .ClusterName }}-tidb.{{ .Namespace }}"
- "*.{{ .ClusterName }}-tidb"
- "{{ .ClusterName }}-tidb.{{ .Namespace }}.svc"
- "*.{{ .ClusterName }}-tidb.{{ .Namespace }}"
- "*.{{ .ClusterName }}-tidb.{{ .Namespace }}.svc"
ipAddresses:
- 127.0.0.1
- ::1
issuerRef:
name: {{ .ClusterName }}-tidb-issuer
kind: Issuer
group: cert-manager.io
---
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
name: {{ .ClusterName }}-tidb-client-secret
namespace: {{ .Namespace }}
spec:
secretName: {{ .ClusterName }}-tidb-client-secret
duration: 8760h # 365d
renewBefore: 360h # 15d
organization:
- PingCAP
commonName: "TiDB Client"
usages:
- client auth
issuerRef:
name: {{ .ClusterName }}-tidb-issuer
kind: Issuer
group: cert-manager.io
`

type tidbClusterTmplMeta struct {
Namespace string
ClusterName string
}

func installCertManager(cli clientset.Interface) error {
cmd := "kubectl apply -f /cert-manager.yaml --validate=false"
if data, err := exec.Command("sh", "-c", cmd).CombinedOutput(); err != nil {
return fmt.Errorf("failed to install cert-manager %s %v", string(data), err)
}

err := e2epod.WaitForPodsRunningReady(cli, "cert-manager", 3, 0, 10*time.Minute, nil)
if err != nil {
return err
}

// It may take a minute or so for the TLS assets required for the webhook to function to be provisioned.
time.Sleep(time.Minute)
return nil
}

func deleteCertManager(cli clientset.Interface) error {
cmd := "kubectl delete -f /cert-manager.yaml"
if data, err := exec.Command("sh", "-c", cmd).CombinedOutput(); err != nil {
return fmt.Errorf("failed to delete cert-manager %s %v", string(data), err)
}

return wait.PollImmediate(5*time.Second, 10*time.Minute, func() (bool, error) {
podList, err := cli.CoreV1().Pods("cert-manager").List(metav1.ListOptions{})
if err != nil {
return false, nil
}
for _, pod := range podList.Items {
err := e2epod.WaitForPodNotFoundInNamespace(cli, pod.Name, "cert-manager", 5*time.Minute)
if err != nil {
framework.Logf("failed to wait for pod cert-manager/%s disappear", pod.Name)
return false, nil
}
}

return true, nil
})
}

func installTiDBIssuer(ns, tcName string) error {
return installCert(tidbIssuerTmpl, ns, tcName)
}

func installTiDBCertificates(ns, tcName string) error {
return installCert(tidbCertificatesTmpl, ns, tcName)
}

func installCert(tmplStr, ns, tcName string) error {
var buf bytes.Buffer
tmpl, err := template.New("template").Parse(tmplStr)
if err != nil {
return fmt.Errorf("error when parsing template: %v", err)
}
err = tmpl.Execute(&buf, tidbClusterTmplMeta{ns, tcName})
if err != nil {
return fmt.Errorf("error when executing template: %v", err)
}

tmpFile, err := ioutil.TempFile(os.TempDir(), "tls-")
if err != nil {
return err
}
_, err = tmpFile.Write(buf.Bytes())
if err != nil {
return err
}
if data, err := exec.Command("sh", "-c", fmt.Sprintf("kubectl apply -f %s", tmpFile.Name())).CombinedOutput(); err != nil {
framework.Logf("failed to create certificate: %s, %v", string(data), err)
return err
}

return nil
}

func tidbIsTLSEnabled(fw portforward.PortForward, c clientset.Interface, ns, tcName, passwd string) wait.ConditionFunc {
return func() (bool, error) {
secretName := util.TiDBClientTLSSecretName(tcName)
secret, err := c.CoreV1().Secrets(ns).Get(secretName, metav1.GetOptions{})
if err != nil {
return false, err
}

rootCAs := x509.NewCertPool()
rootCAs.AppendCertsFromPEM(secret.Data[v1.ServiceAccountRootCAKey])

clientCert, certExists := secret.Data[v1.TLSCertKey]
clientKey, keyExists := secret.Data[v1.TLSPrivateKeyKey]
if !certExists || !keyExists {
return false, fmt.Errorf("cert or key does not exist in secret %s/%s", ns, secretName)
}

tlsCert, err := tls.X509KeyPair(clientCert, clientKey)
if err != nil {
return false, fmt.Errorf("unable to load certificates from secret %s/%s: %v", ns, secretName, err)
}
err = mysql.RegisterTLSConfig("tidb-server-tls", &tls.Config{
RootCAs: rootCAs,
Certificates: []tls.Certificate{tlsCert},
InsecureSkipVerify: true,
})
if err != nil {
return false, err
}

localHost, localPort, cancel, err := portforward.ForwardOnePort(fw, ns, fmt.Sprintf("svc/%s", controller.TiDBMemberName(tcName)), 4000)
if err != nil {
return false, err
}
defer cancel()

db, err := sql.Open("mysql",
fmt.Sprintf("root:%s@(%s:%d)/test?tls=tidb-server-tls", passwd, localHost, localPort))
if err != nil {
return false, err
}

defer db.Close()
if err := db.Ping(); err != nil {
return false, err
}

rows, err := db.Query("SHOW STATUS")
if err != nil {
return false, err
}
var name, value string
for rows.Next() {
err := rows.Scan(&name, &value)
if err != nil {
return false, err
}

if name == "Ssl_cipher" {
if value == "" {
return true, fmt.Errorf("the connection to tidb server is not ssl %s/%s", ns, tcName)
}

framework.Logf("The connection to TiDB Server is TLS enabled.")
return true, nil
}
}

return true, fmt.Errorf("can't find Ssl_cipher in status %s/%s", ns, tcName)
}
}
2 changes: 1 addition & 1 deletion tests/e2e/util/image/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const (
TiDBV3UpgradeVersion = "v3.0.9"
TiDBTLSVersion = TiDBV3Version // must >= 3.0.5
TiDBV2Version = "v2.1.19"
TiDBBRVersion = "v4.0.0-beta.1"
TiDBV4Version = "v4.0.0-rc"
)

func ListImages() []string {
Expand Down
1 change: 1 addition & 0 deletions tests/images/e2e/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ RUN curl https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VER
RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \
unzip awscliv2.zip && \
./aws/install
RUN curl -L "https://github.com/jetstack/cert-manager/releases/download/v0.11.1/cert-manager.yaml" -o "/cert-manager.yaml"

ADD tidb-operator /charts/e2e/tidb-operator
ADD tidb-cluster /charts/e2e/tidb-cluster
Expand Down
Loading

0 comments on commit a6bbe4c

Please sign in to comment.