# Create TLS Certificates Using CFSSL

> - **Objective:** Generate Certificate for TLS Using cfssl
> - **Prerequisite:**
>     - None
> - **Optionality:** Optional
> - **Estimated time:** 30min

## Introduction

This guide helps you create TLS certificates as well as Kubernetes Secrets. The two separate sets of TLS certificates created can be used for two purposes: 1) connections from MySQL-compatible clients to the TiDB service, and 2) connections between the components of the TiDB cluster. After creating the certificates and Secrets, follow the instructions for [configuring security when you Deploy a TiDB Cluster](/hc/articles/360049552073#security).

TLS certificates can be issued by multiple methods. This document describes how to use [CFSSL](https://github.com/cloudflare/cfssl) to issue certificates.

CFSSL is CloudFlare's PKI/TLS swiss army knife. It is both a command line tool and an HTTP API server for signing, verifying, and bundling TLS certificates.

## Install CFSSL

You can install `cfssl` and `cfssljson` in the current directory, or into any directory already in your PATH for ease of use:

- Linux

  Your OS's package manager may have a package for `cfssl`. If not, manually install the `cfssl` and `cfssljson` to a location of your choice that is in your PATH:

  ```{.bash .copyable}
  curl -s -L -o cfssl https://pkg.cfssl.org/R1.2/cfssl_linux-amd64
  curl -s -L -o cfssljson https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64
  chmod +x cfssl cfssljson
  ```

- macOS

  On macOS, it's easiest to use [Homebrew](https://brew.sh) to install CFSSL (this also installs `cfssljson`):

  ```{.bash .copyable}
  brew install cfssl
  ```

Verify the installation：

```{.bash .copyable}
cfssl -h
```

```{.output}
Usage:
Available commands:
        version
        ocsprefresh
        scan
        bundle
        sign
        selfsign
        crl
        gencert
        ocspserve
        info
        genkey
        ocspsign
        gencsr
        gencrl
        ocspdump
        print-defaults
        revoke
        certinfo
        serve
Top-level flags:
```

If the `cfssl` executable can't be found, add the directory where you downloaded it to your PATH variable:

```{.bash .copyable}
export PATH=$PATH:$PWD
```

## Issue Certificate

### Create Certificate Directory 

Create a directory to hold all certificates. You can use any directory you like, as long as you `cd` to it before executing the later commands in this document.

```{.bash .copyable}
certificate_path=~/cfssl_workdir
mkdir -p "$certificate_path"
cd "$certificate_path"
```

**Note that the following operations are performed under `${certificate_path}`.**

### Configure Certificate Authority (CA)

A Certificate Authority (CA) is an entity that issues and signs digital certificates. A digital certificate certifies the ownership of a public key by the named subject of the certificate. This allows others (relying parties) to rely upon signatures or on assertions made about the private key that corresponds to the certified public key. A CA acts as a trusted third party—trusted both by the subject (owner) of the certificate and by the party relying upon the certificate. The format of these certificates is specified by the X.509 or EMV standard.

We first need to create and customize the `ca-config.json` configuration file to define our CA.

```{.bash .copyable}
cfssl print-defaults config > ca-config.json
```

Edit `ca-config.json` to make modifications. The changes we need to make are:

- Change `sigining.default.expiry` to `8760h`.
- Change `sigining.profiles.www` to `server`.
- Change `signing.profiles.server.usage` to add `server auth` and `client auth`

```{.json .copyable}
{
    "signing": {
        "default": {
            "expiry": "8760h"
        },
        "profiles": {
            "server": {
                "expiry": "8760h",
                "usages": [
                    "signing",
                    "key encipherment",
                    "server auth",
                    "client auth"
                ]
            },
            "client": {
                "expiry": "8760h",
                "usages": [
                    "signing",
                    "key encipherment",
                    "client auth"
                ]
            }
        }
    }
}
```

> **Note**
>
> We need to add `client auth` in profiles.server.usages, because this server-side certificate is also used as a client certificate

Next we need to configure the Certificate Signing Request (CSR):

```{.bash .copyable}
cfssl print-defaults csr > ca-csr.json
```

We need to make some modifications to the CSR:
 
- Change the `CN` field to `TIDB`.
- Change the `key.algo` filed to `rsa` and `key.size` to `2048`.
- Change the names to the values below.

```{.json .copyable}
{
    "CN": "TiDB",
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "US",
            "L": "CA",
            "O": "PingCAP",
            "ST": "Beijing",
            "OU": "TiDB"
        }
    ]
}
```

Now generate the self-signed root CA certificate and private key:

```{.bash .copyable}
cfssl gencert -initca ca-csr.json | cfssljson -bare ca -
```

```{.output}
2020/04/28 11:32:55 [INFO] generating a new CA key and certificate from CSR
2020/04/28 11:32:55 [INFO] generate received request
2020/04/28 11:32:55 [INFO] received CSR
2020/04/28 11:32:55 [INFO] generating key: rsa-2048
2020/04/28 11:32:55 [INFO] encoded CSR
2020/04/28 11:32:55 [INFO] signed certificate with serial number 312652930147915274584885165112010854762084891048
```

#### Troubleshooting

- [WARNING] This certificate lacks a "hosts" field.

    This is normal because we are not trying to provide web services.

### Issue Certificates

We need to generate certificates for inter and intra component communication. In summary, we need to generate the following certificates:

- PD Server certificate for communicating between PD instances.
- TiKV Server certificate for communicating between TiKV instances.
- TiDB Server certificate for communicating between MySQL client and TiDB Cluster. 
- Cluster Client certificate for communicating between components.
- Client certificate for communicating between MySQL client and TiDB Cluster. 

#### Issue PD Server Certificate

```{.bash .copyable}
cfssl print-defaults csr > pd-server.json
```

Edit `pd-server.json` to make modifications. The changes we need to make are:

- Change the `CN` field to `TiDB`.
- Change the `hosts` field to add the following. Note that the entries in hosts assumes the TiDB cluster name to be `my-cluster` and namespace to be `poc`. You need to make corresponding changes. 

```{.json .copyable}
{
    "CN": "TiDB",
    "hosts": [
        "127.0.0.1",
        "::1",
        "my-cluster-pd",
        "my-cluster-pd.poc",
        "my-cluster-pd.poc.svc",
        "my-cluster-pd-peer",
        "my-cluster-pd-peer.poc",
        "my-cluster-pd-peer.poc.svc",
        "*.my-cluster-pd-peer",
        "*.my-cluster-pd-peer.poc",
        "*.my-cluster-pd-peer.poc.svc"
    ],
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "US",
            "L": "CA",
            "O": "PingCAP",
            "ST": "Beijing",
            "OU": "TiDB"
        }
    ]
}
```

We can then generate the PD Server certificate with the CA:

```{.bash .copyable}
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=server pd-server.json | cfssljson -bare pd-server
```

```{.output}
2020/04/23 22:55:06 [INFO] generate received request
2020/04/23 22:55:06 [INFO] received CSR
2020/04/23 22:55:06 [INFO] generating key: rsa-2048
2020/04/23 22:55:07 [INFO] encoded CSR
2020/04/23 22:55:07 [INFO] signed certificate with serial number 214389999857155365314767437017019726628791998243
```

We can inspect the certificate using `openssl`:

```{.bash .copyable}
openssl x509 -text -noout -in pd-server.pem
```

```{.output}
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            42:a7:a6:4e:32:9a:42:5c:3f:a0:66:da:60:d0:82:d7:5d:ed:54:c7
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=US, ST=Beijing, L=CA, O=PingCAP, OU=TiDB, CN=TiDB
        Validity
            Not Before: Apr 25 03:27:00 2020 GMT
            Not After : Apr 25 03:27:00 2021 GMT
        Subject: C=US, ST=Beijing, L=CA, O=PingCAP, OU=TiDB, CN=TiDB
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:dc:f9:9f:74:ac:eb:2d:66:44:81:b2:ba:e4:f5:
                    71:a5:3b:b1:ef:0f:f8:d2:0f:26:9a:b8:6e:03:3b:
                    65:74:73:ea:a2:ae:0f:93:d8:6a:a9:a3:a5:7f:eb:
                    a1:10:76:43:1f:25:dd:2c:1f:f5:23:a1:43:6f:e9:
                    30:d8:d4:ac:e2:a3:53:08:28:82:dc:d1:bf:e8:59:
                    96:f8:3c:dd:a4:65:53:02:a6:ed:66:13:7d:16:10:
                    ad:ad:b3:f5:50:fd:fb:fb:74:cb:c3:b3:90:d7:b1:
                    0f:22:85:6d:13:2f:c0:7d:ee:8b:17:cd:75:82:ea:
                    16:db:ca:97:4c:6c:00:94:74:c2:da:07:48:6f:db:
                    90:75:19:58:54:16:de:08:bc:c5:a0:62:e7:f4:69:
                    4f:57:31:5f:e4:ce:ca:83:6d:84:dd:64:39:d8:1e:
                    04:e1:64:70:ec:f6:47:b3:22:d1:57:4f:bc:a9:4f:
                    54:b8:29:c9:8b:64:7a:96:90:3f:98:14:be:26:b1:
                    a3:7c:fa:a1:4d:df:9a:c5:e4:37:2a:2b:41:70:ae:
                    57:82:7f:89:a3:50:56:5e:80:82:e9:e4:09:7e:d8:
                    8f:4f:ab:03:87:09:af:f7:31:bc:0a:2a:20:6e:3a:
                    e5:a3:95:4d:f5:19:71:cc:6d:d3:a9:88:29:21:ba:
                    51:e1
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage:
                TLS Web Server Authentication, TLS Web Client Authentication
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Subject Key Identifier:
                3C:29:6B:01:2D:98:59:D8:E5:A7:F0:10:7E:8F:41:E9:98:F1:F1:ED
            X509v3 Subject Alternative Name:
                DNS:my-cluster-pd, DNS:my-cluster-pd.poc, DNS:my-cluster-pd.poc.svc, DNS:my-cluster-pd-peer, DNS:my-cluster-pd-peer.poc, DNS:my-cluster-pd-peer.poc.svc, DNS:*.my-cluster-pd-peer, DNS:*.my-cluster-pd-peer.poc, DNS:*.my-cluster-pd-peer.poc.svc, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1
    Signature Algorithm: sha256WithRSAEncryption
         94:cf:8f:af:15:b2:4d:de:64:8c:b0:58:dd:b8:ab:8d:99:48:
         23:50:29:47:27:59:af:3c:f8:17:06:ba:a8:0b:be:30:03:b0:
         c0:f2:60:1c:60:4c:cd:0a:bb:9b:3d:35:7c:43:74:87:1d:51:
         6e:ed:d8:d4:fe:88:04:d9:95:58:4e:8d:40:92:6e:b8:84:13:
         b3:c2:83:d7:dc:38:41:7b:ce:8a:99:6f:13:fb:eb:d4:a6:2e:
         f7:80:75:71:8d:03:56:66:a6:d3:1c:f9:83:3e:76:b7:d3:68:
         6b:46:fe:d5:9d:7b:e2:60:f9:d3:f5:40:fd:42:de:ed:b5:02:
         3b:4f:15:87:25:fc:90:5d:2d:71:74:0b:b8:56:6b:bd:33:74:
         8e:76:ce:54:83:a8:0a:1e:dc:60:f0:73:d0:a1:cf:01:00:c6:
         2a:f4:8a:25:c2:d9:50:39:08:f2:8f:fb:21:6e:9a:9d:04:c6:
         86:5a:b4:c9:c8:c4:66:a6:9d:5d:0d:66:2e:92:83:c9:62:8f:
         21:cb:68:08:2d:8c:bb:bc:7d:39:c5:6c:04:a0:aa:bc:2c:0d:
         6c:39:50:12:7d:37:a5:e3:ba:5e:09:2f:a7:07:67:68:3b:67:
         6e:db:f6:f8:75:8e:1b:13:94:80:1d:f8:05:8a:2d:94:70:ea:
         04:c7:09:8c
```

#### Issue TiKV Server certificate

```{.bash .copyable}
cfssl print-defaults csr > tikv-server.json
```

Edit `tikv-server.json` to make modifications. The changes we need to make are:

- Change the `CN` field to `TiDB`.
- Change the `hosts` field to add the following.
- Note that the entries in hosts assumes the TiDB cluster name to be `my-cluster` and namespace to be `poc`. Change to match your deployment.

```{.json .copyable}
{
    "CN": "TiDB",
    "hosts": [
      "127.0.0.1",
      "::1",
      "my-cluster-tikv",
      "my-cluster-tikv.poc",
      "my-cluster-tikv.poc.svc",
      "my-cluster-tikv-peer",
      "my-cluster-tikv-peer.poc",
      "my-cluster-tikv-peer.poc.svc",
      "*.my-cluster-tikv-peer",
      "*.my-cluster-tikv-peer.poc",
      "*.my-cluster-tikv-peer.poc.svc"
    ],
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "US",
            "L": "CA",
            "O": "PingCAP",
            "ST": "Beijing",
            "OU": "TiDB"
        }
    ]
}
```

We can then generate the TiKV Server certificate with the CA:

```{.bash .copyable}
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=server tikv-server.json | cfssljson -bare tikv-server
```

```{.output}
2020/04/28 12:55:42 [INFO] generate received request
2020/04/28 12:55:42 [INFO] received CSR
2020/04/28 12:55:42 [INFO] generating key: rsa-2048
2020/04/28 12:55:42 [INFO] encoded CSR
2020/04/28 12:55:42 [INFO] signed certificate with serial number 425207623158563460200719671690261814210346624866
```

We can inspect the certificate using `openssl`:

```{.bash .copyable}
openssl x509 -text -in tikv-server.pem -noout
```

#### Issue TiDB Server Certificate

```{.bash .copyable}
cfssl print-defaults csr > tidb-server.json
```

Edit `tidb-server.json` to make modifications. The changes we need to make are:

- Change the `CN` field to `TiDB`.
- Change the `hosts` field to add the following.
- Note that the entries in hosts assumes the TiDB cluster name to be `my-cluster` and namespace to be `poc`. You need to make corresponding changes.

```{.json .copyable}
{
    "CN": "TiDB",
    "hosts": [
        "127.0.0.1",
        "::1",
        "my-cluster-tidb",
        "my-cluster-tidb.poc",
        "my-cluster-tidb.poc.svc",
        "*.my-cluster-tidb",
        "*.my-cluster-tidb.poc",
        "*.my-cluster-tidb.poc.svc"
    ],
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "US",
            "L": "CA",
            "O": "PingCAP",
            "ST": "Beijing",
            "OU": "TiDB"
        }
    ]
}
```

We can then generate the TiDB Server certificate with the CA:

```{.bash .copyable}
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=server tidb-server.json | cfssljson -bare tidb-server
```

```{.output}
2020/04/28 13:02:06 [INFO] generate received request
2020/04/28 13:02:06 [INFO] received CSR
2020/04/28 13:02:06 [INFO] generating key: rsa-2048
2020/04/28 13:02:07 [INFO] encoded CSR
2020/04/28 13:02:07 [INFO] signed certificate with serial number 677609381869553541316763926377530091236235435217
```

We can inspect the certificate using `openssl`:

```{.bash .copyable}
openssl x509 -text -in tidb-server.pem -noout
```

#### Issue Client Certificate

```{.bash .copyable}
cfssl print-defaults csr > client.json
```

Edit `client.json` to make modifications. The changes we need to make are:

- Change the `CN` field to `TiDB`.
- Change the `hosts` field to be empty.

```{.json .copyable}
{
    "CN": "TiDB",
    "hosts": [],
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "US",
            "L": "CA",
            "O": "PingCAP",
            "ST": "Beijing",
            "OU": "TiDB"
        }
    ]
}
```

```{.bash .copyable}
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=client client.json | cfssljson -bare client
```

```{.output}
2020/04/28 13:18:12 [INFO] generate received request
2020/04/28 13:18:12 [INFO] received CSR
2020/04/28 13:18:12 [INFO] generating key: rsa-2048
2020/04/28 13:18:12 [INFO] encoded CSR
2020/04/28 13:18:12 [INFO] signed certificate with serial number 18096070442089977328487840082232485650921646783
2020/04/28 13:18:12 [WARNING] This certificate lacks a "hosts" field. This makes it unsuitable for
websites. For more information see the Baseline Requirements for the Issuance and Management
of Publicly-Trusted Certificates, v.1.1.6, from the CA/Browser Forum (https://cabforum.org);
specifically, section 10.2.3 ("Information Requirements").
```

We can inspect the certificate using `openssl`:

```{.bash .copyable}
openssl x509 -text -noout -in client.pem
```

### Create Kubernetes Secret

Before performing the operations, please ensure that a Kubernetes cluster (but not a TiDB cluster) is up and running, and that the following variables have been set:

```{.bash .copyable}
cluster_name=my-cluster
namespace=poc
certificate_path=${certificate_path} # Already set from an earlier step
```

If you haven't yet created the namespace for your TiDB cluster, do so now:

```{.bash .copyable}
kubectl create namespace "$namespace"
```

#### Create Secret for TiDB Cluster Components

##### Create PD Server certificate

```{.bash .copyable}
kubectl create secret generic ${cluster_name}-pd-cluster-secret \
  --namespace=${namespace} --from-file=tls.crt=${certificate_path}/pd-server.pem \
  --from-file=tls.key=${certificate_path}/pd-server-key.pem \
  --from-file=ca.crt=${certificate_path}/ca.pem
```

##### Create TiKV Server certificate

```{.bash .copyable}
kubectl create secret generic ${cluster_name}-tikv-cluster-secret \
  --namespace=${namespace} --from-file=tls.crt=${certificate_path}/tikv-server.pem \
  --from-file=tls.key=${certificate_path}/tikv-server-key.pem \
  --from-file=ca.crt=${certificate_path}/ca.pem
```

##### Create TiDB Server certificate

```{.bash .copyable}
kubectl create secret generic ${cluster_name}-tidb-cluster-secret \
  --namespace=${namespace} --from-file=tls.crt=${certificate_path}/tidb-server.pem \
  --from-file=tls.key=${certificate_path}/tidb-server-key.pem \
  --from-file=ca.crt=${certificate_path}/ca.pem
```

##### Create Cluster Client certificate

```{.bash .copyable}
kubectl create secret generic ${cluster_name}-cluster-client-secret \
  --namespace=${namespace} --from-file=tls.crt=${certificate_path}/client.pem \
  --from-file=tls.key=${certificate_path}/client-key.pem \
  --from-file=ca.crt=${certificate_path}/ca.pem
```

#### Create Secret for MySQL Client and TiDB Cluster

##### Create Server certificate

```{.bash .copyable}
kubectl create secret generic ${cluster_name}-tidb-server-secret \
  --namespace=${namespace} --from-file=tls.crt=${certificate_path}/tidb-server.pem \
  --from-file=tls.key=${certificate_path}/tidb-server-key.pem \
  --from-file=ca.crt=${certificate_path}/ca.pem
```

##### Create Client certificate

```{.bash .copyable}
kubectl create secret generic ${cluster_name}-tidb-client-secret \
  --namespace=${namespace} --from-file=tls.crt=${certificate_path}/client.pem \
  --from-file=tls.key=${certificate_path}/client-key.pem \
  --from-file=ca.crt=${certificate_path}/ca.pem
```

> **Note**
>
> If you want to be able to connect using TLS from an application in another Kubernetes namespace, you should also create a client secret in the application's namespace.

### Verify that the secrets are created

```{.bash .copyable}
kubectl get secrets -n "$namespace"
```

```{.output}
NAME                               TYPE                                  DATA   AGE
my-cluster-cluster-client-secret   Opaque                                3      27s
my-cluster-pd-cluster-secret       Opaque                                3      51s
my-cluster-tidb-client-secret      Opaque                                3      14s
my-cluster-tidb-cluster-secret     Opaque                                3      33s
my-cluster-tidb-server-secret      Opaque                                3      20s
my-cluster-tikv-cluster-secret     Opaque                                3      44s
default-token-jwh9v                kubernetes.io/service-account-token   3      56s
```
