etcd 是雲原生架構中重要的基礎組件,由 CNCF 孵化託管。etcd 在微服務和 Kubernates 集羣中不僅可以作爲服務註冊與發現,還可以作爲 key-value 存儲的中間件。

《徹底搞懂 etcd 系列文章》將會從 etcd 的基本功能實踐、API 接口、實現原理、源碼分析,以及實現中的踩坑經驗等幾方面具體展開介紹 etcd。預計會有 20 篇左右的文章,筆者將會每週持續更新,歡迎關注。

1 etcd 安全

在上一篇文章介紹了 etcd 集羣的運維部署之後,本文將會重點講解 etcd 的安全通信實踐。 etcd 支持通過 TLS 協議進行的加密通信。TLS 通道可用於對等體之間的加密內部羣集通信以及加密的客戶端流量。本文提供了使用對等和客戶端 TLS 設置羣集的示例。

2 TLS 與 SSL

互聯網的通信安全,建立在 SSL/TLS 協議之上。不使用 SSL/TLS 的 HTTP 通信,就是不加密的通信。所有信息明文傳播,帶來了三大風險:

  • 竊聽風險(eavesdropping):第三方可以獲知通信內容。
  • 篡改風險(tampering):第三方可以修改通信內容。
  • 冒充風險(pretending):第三方可以冒充他人身份參與通信。

SSL/TLS 協議是爲了解決這三大風險而設計的,希望達到:

  • 所有信息都是加密傳播,第三方無法竊聽。
  • 具有校驗機制,一旦被篡改,通信雙方會立刻發現。
  • 配備身份證書,防止身份被冒充。

下面具體介紹下 SSL 與 TLS 的相關概念:

  • SSL (Secure Socket Layer):爲Netscape所研發,用以保障在Internet上數據傳輸之安全,利用數據加密(Encryption)技術,可確保數據在網絡上之傳輸過程中不會被截取。目前一般通用之規格爲40 bit之安全標準,美國則已推出128 bit之更高安全標準,但限制出境。只要3.0版本以上之I.E.或Netscape瀏覽器即可支持SSL。
  • 安全傳輸層協議(TLS)用於在兩個通信應用程序之間提供保密性和數據完整性。該協議由兩層組成: TLS 記錄協議(TLS Record)和 TLS 握手協議(TLS Handshake)。較低的層爲 TLS 記錄協議,位於某個可靠的傳輸協議(例如 TCP)上面。

想要實現數據 HTTPS 加密協議訪問,保障數據的安全,就需要 SSL 證書,TLS 是 SSL 與 HTTPS 安全傳輸層協議名稱。

3 進行 TLS 加密實踐

爲了進行實踐,我們將會安裝一些實用的命令行工具,其中包括 cfssl、cfssljson。

cfssl 是 CloudFlare 的 PKI/TLS 利器。 它既是命令行工具,又可以用於簽名,驗證和捆綁 TLS 證書的 HTTP API 服務器,環境構建方面需要 Go 1.12+。

cfssljson 程序,從 cfssl 獲取 JSON 輸出,並將證書、密鑰、CSR和 bundle 寫入指定位置。

環境配置

HostName ip 客戶端交互端口 peer 通信端口
infra0 192.168.202.128 2379 2380
infra1 192.168.202.129 2379 2380
infra2 192.168.202.130 2379 2380

3.1 安裝 cfssl

$ ls ~/Downloads/cfssl
cfssl-certinfo_1.4.1_linux_amd64 cfssl_1.4.1_linux_amd64          cfssljson_1.4.1_linux_amd64
chmod +x cfssl_1.4.1_linux_amd64 cfssljson_1.4.1_linux_amd64 cfssl-certinfo_1.4.1_linux_amd64

mv cfssl_1.4.1_linux_amd64 /usr/local/bin/cfssl
mv cfssljson_1.4.1_linux_amd64 /usr/local/bin/cfssljson
mv cfssl-certinfo_1.4.1_linux_amd64 /usr/bin/cfssl-certinfo

安裝完成之後,查看版本信息的結果:

$ cfssl version

Version: 1.4.1
Runtime: go1.12.12

3.2 配置 CA 並創建 TLS 證書

我們將使用 CloudFlare’s PKI 工具 cfssl 來配置 PKI Infrastructure,然後使用它去創建 Certificate Authority(CA), 併爲 etcd 創建 TLS 證書。

首先創建 ssl 配置目錄:

mkdir /opt/etcd/{bin,cfg,ssl} -p
cd /opt/etcd/ssl/

etcd ca 配置:

cat << EOF | tee ca-config.json
{
  "signing": {
    "default": {
      "expiry": "87600h"
    },
    "profiles": {
      "etcd": {
         "expiry": "87600h",
         "usages": [
            "signing",
            "key encipherment",
            "server auth",
            "client auth"
        ]
      }
    }
  }
}
EOF

etcd ca證書:

cat << EOF | tee ca-csr.json
{
    "CN": "etcd CA",
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "L": "Shanghai",
            "ST": "Shanghai"
        }
    ]
}
EOF

生成 CA 憑證和私鑰:

$ cfssl gencert -initca ca-csr.json | cfssljson -bare ca

2020/04/30 20:36:58 [INFO] generating a new CA key and certificate from CSR
2020/04/30 20:36:58 [INFO] generate received request
2020/04/30 20:36:58 [INFO] received CSR
2020/04/30 20:36:58 [INFO] generating key: rsa-2048
2020/04/30 20:36:58 [INFO] encoded CSR
2020/04/30 20:36:58 [INFO] signed certificate with serial number 252821789025044258332210471232130931231440888312

$ ls

ca-config.json  ca-csr.json  ca-key.pem  ca.csr  ca.pem

etcd server證書:

cat << EOF | tee server-csr.json
{
    "CN": "etcd",
    "hosts": [
    "192.168.202.128",
    "192.168.202.129",
    "192.168.202.130"
    ],
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "L": "Beijing",
            "ST": "Beijing"
        }
    ]
}
EOF

生成 server 證書:

cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=etcd server-csr.json | cfssljson -bare server
2020/04/30 20:44:37 [INFO] generate received request
2020/04/30 20:44:37 [INFO] received CSR
2020/04/30 20:44:37 [INFO] generating key: rsa-2048
2020/04/30 20:44:37 [INFO] encoded CSR
2020/04/30 20:44:37 [INFO] signed certificate with serial number 73061688633166283265484923779818839258466531108

ls
ca-config.json  ca-csr.json  ca-key.pem  ca.csr  ca.pem  server-csr.json  server-key.pem  server.csr  server.pem

啓動 etcd 集羣,配置如下:

#etcd1 啓動

$ /opt/etcd/bin/etcd --name etcd1 --initial-advertise-peer-urls https://192.168.202.128:2380 \
     --listen-peer-urls https://192.168.202.128:2380 \
     --listen-client-urls https://192.168.202.128:2379,https://127.0.0.1:2379 \
     --advertise-client-urls https://192.168.202.128:2379 \
     --initial-cluster-token etcd-cluster-1 \
     --initial-cluster etcd1=https://192.168.202.128:2380, etcd2=https://192.168.202.129:2380, etcd3=https://192.168.202.130:2380 \
     --initial-cluster-state new \
     --client-cert-auth --trusted-ca-file=/opt/etcd/ssl/ca.pem \
     --cert-file=/opt/etcd/ssl/server.pem --key-file=/opt/etcd/ssl/server-key.pem \
     --peer-client-cert-auth --peer-trusted-ca-file=/opt/etcd/ssl/ca.pem \
     --peer-cert-file=/opt/etcd/ssl/server.pem --peer-key-file=/opt/etcd/ssl/server-key.pem

#etcd2 啓動
/opt/etcd/bin/etcd --name etcd2 --initial-advertise-peer-urls https://192.168.202.129:2380 \
      --listen-peer-urls https://192.168.202.129:2380 \
      --listen-client-urls https://192.168.202.129:2379,https://127.0.0.1:2379 \
      --advertise-client-urls https://192.168.202.129:2379 \
      --initial-cluster-token etcd-cluster-1 \
      --initial-cluster etcd1=https://192.168.202.128:2380, etcd2=https://192.168.202.129:2380, etcd3=https://192.168.202.130:2380 \
      --initial-cluster-state new \
      --client-cert-auth --trusted-ca-file=/opt/etcd/ssl/ca.pem \
      --cert-file=/opt/etcd/ssl/server.pem --key-file=/opt/etcd/ssl/server-key.pem \
      --peer-client-cert-auth --peer-trusted-ca-file=/opt/etcd/ssl/ca.pem \
      --peer-cert-file=/opt/etcd/ssl/server.pem --peer-key-file=/opt/etcd/ssl/server-key.pem
#etcd3 啓動
/opt/etcd/bin/etcd --name etcd3 --initial-advertise-peer-urls https://192.168.202.130:2380 \
       --listen-peer-urls https://192.168.202.130:2380 \
       --listen-client-urls https://192.168.202.130:2379,https://127.0.0.1:2379 \
       --advertise-client-urls https://192.168.202.130:2379 \
       --initial-cluster-token etcd-cluster-1 \
       --initial-cluster etcd1=https://192.168.202.128:2380, etcd2=https://192.168.202.129:2380, etcd3=https://192.168.202.130:2380 \
       --initial-cluster-state new \
       --client-cert-auth --trusted-ca-file=/opt/etcd/ssl/ca.pem \
       --cert-file=/opt/etcd/ssl/server.pem --key-file=/opt/etcd/ssl/server-key.pem \
       --peer-client-cert-auth --peer-trusted-ca-file=/opt/etcd/ssl/ca.pem \
       --peer-cert-file=/opt/etcd/ssl/server.pem --peer-key-file=/opt/etcd/ssl/server-key.pem

通過三臺服務器的控制檯可以知道,集羣已經成功建立,我們進行驗證:

$ /opt/etcd/bin/etcdctl --cacert=/opt/etcd/ssl/ca.pem --cert=/opt/etcd/ssl/server.pem --key=/opt/etcd/ssl/server-key.pem --endpoints="https://192.168.202.128:2379,https://192.168.202.129:2379,https://192.168.202.130:2379"  endpoint health

# 輸出如下:
https://192.168.202.129:2379 is healthy: successfully committed proposal: took = 9.492956ms
https://192.168.202.130:2379 is healthy: successfully committed proposal: took = 12.805109ms
https://192.168.202.128:2379 is healthy: successfully committed proposal: took = 13.036091ms

查看三個節點的健康狀況, endpoint health ,輸出的結果符合我們的預期。其次,查看集羣的成員列表:

$ /opt/etcd/bin/etcdctl --cacert=/opt/etcd/ssl/ca.pem --cert=/opt/etcd/ssl/server.pem --key=/opt/etcd/ssl/server-key.pem --endpoints="https://192.168.202.128:2379,https://192.168.202.129:2379,https://192.168.202.130:2379" member list

# 輸出如下:
48e15f7612b3de1, started, etcd2, https://192.168.202.129:2380, https://192.168.202.129:2379, false
6b57a3c3b8a54873, started, etcd3, https://192.168.202.130:2380, https://192.168.202.130:2379, false
c1ba2629c5bc62ac, started, etcd1, https://192.168.202.128:2380, https://192.168.202.128:2379, false

輸出三個成員,完全符合我們的預期。經過 TLS 加密的 etcd 集羣,在進行操作時,需要加上認證相關的信息,我們嘗試先寫再讀的操作:

$ /opt/etcd/bin/etcdctl --cacert=/opt/etcd/ssl/ca.pem --cert=/opt/etcd/ssl/server.pem --key=/opt/etcd/ssl/server-key.pem --endpoints="https://192.168.202.128:2379,https://192.168.202.129:2379,https://192.168.202.130:2379" put hello world

OK

$ /opt/etcd/bin/etcdctl --cacert=/opt/etcd/ssl/ca.pem --cert=/opt/etcd/ssl/server.pem --key=/opt/etcd/ssl/server-key.pem --endpoints="https://192.168.202.128:2379,https://192.168.202.129:2379,https://192.168.202.130:2379" get hello

hello
world

寫入 hello->wold 的鍵值對,讀取的時候,控制檯正常輸出了鍵值。至此,我們成功將 etcd 的通信加密。

3.3 自動證書

如果集羣需要加密的通信但不需要經過身份驗證的連接,則可以將 etcd 配置爲自動生成其密鑰。 在初始化時,每個成員都基於其通告的 IP 地址和主機創建自己的密鑰集。

在每臺機器上,etcd 將使用以下標誌啓動:

$ etcd --name etcd1 --initial-advertise-peer-urls https://192.168.202.128:2380 \
  --listen-peer-urls https://192.168.202.128:2380 \
  --listen-client-urls https://192.168.202.128:2379,https://127.0.0.1:2379 \
  --advertise-client-urls https://10.0.1.10:2379 \
  --initial-cluster-token etcd-cluster-1 \
  --initial-cluster infra0=https://192.168.202.128:2380,infra1=https://192.168.202.129:2380,infra2=https://192.168.202.130:2380 \
  --initial-cluster-state new \
  --auto-tls \
  --peer-auto-tls

注意,由於自動簽發證書並不認證身份,因此直接 curl 會返回錯誤。需要使用 curl 的 -k 命令屏蔽對證書鏈的校驗。

相關文章