Protect the Remote Docker Server
前言
上一篇文章討論了如何將 docker engine 開啟遠端控制的功能,此功能常被用在一些輔助管理 docker 容器的應用,如 Portainer。
然而之前也提到此種做法是有風險的,其中最重要的就是原本的 docker socket 會受到 Linux 的權限限制,但暴露到網路上後就沒有這層保護了。
其次,由於使用 HTTP 來傳輸,一些機敏資訊都會以明碼的方式來傳輸。以下使用 docker login
命令搭配 WireShark 抓包讓各位感受一下。
在 Terminal 上進行 docker login
,看似有隱藏起密碼。
但在 WireShark 上看得一清二楚。
以上兩個顯然都是需要被改善的問題。
這邊將會介紹兩種方式來解決安全性的問題。
Using HTTPS
前面提到了使用了 http 導致封包是明碼的,應該可以很直覺地聯想到改用 https 來解決這個問題。
首先要知道的是在配置 https 的時候,會需要在 server 端及 client 都用同一個 CA 去做簽章。這邊會著重在 docker 的配置操作,關於憑證信任鏈的知識就不多做說明。
註:基於安全性考量,以下操作憑證簽署會使用 root 身分,唯 client 端憑證須給對應 user 使用需要修改檔案 owner。
建立 CA
由於是自簽的憑證,CA 也由我們自己產生。
首先產出一個私鑰:
openssl genrsa -out ca.key 4096
如果有需要用 pass phrase 加密私鑰的話可以改用以下指令:
openssl genrsa -aes256 -out ca.key 4096
接著使用這把私鑰產生自我簽署的 CA 憑證 (視需求調整 subj
部分):
openssl req -new -x509 -days 365 -sha256 \
-subj "/C=TW/ST=Taipei/O=TPI/OU=BD4/CN=$HOSTNAME" \
-key ca.key \
-out ca.crt
至此,一個私人 CA 就建立完成了。
簽署 Docker Server 憑證
先產出屬於 docker server 的私鑰:
openssl genrsa -out server.key 4096
用此私鑰產出憑證簽章請求:
openssl req -sha256 -new \
-subj "/CN=$HOSTNAME" \
-key server.key \
-out server.csr
注意:這邊的 CN 必須是你用來連到 docker server 的連線資訊,FQDN 或者 IP。
產出擴充文件:
cat << EOF > extfile.cnf
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = $HOSTNAME
IP.1 = 192.168.80.17
IP.2 = 127.0.0.1
EOF
接著使用 CA 憑證對此請求進行簽章:
openssl x509 -req -sha256 \
-CAcreateserial \
-days 365 \
-extfile extfile.cnf \
-in server.csr \
-CA ca.crt \
-CAkey ca.key \
-out server.crt
配置 Docker Client 憑證
老樣子依序產出私鑰、簽章請求以及憑證:
openssl genrsa -out client.key 4096
openssl req -new \
-subj "/CN=$HOSTNAME" \
-key client.key \
-out client.csr
產出擴充文件:
cat << EOF > extfile-client.cnf
extendedKeyUsage = clientAuth
EOF
使用 CA 憑證對此請求進行簽章:
openssl x509 -req -sha256 \
-CAcreateserial \
-days 365 \
-extfile extfile-client.cnf \
-in client.csr \
-CA ca.crt \
-CAkey ca.key \
-out client.crt
將 client 端的私鑰及憑證,連同 server 的憑證複製到 client 端的機器上。
啟用 HTTPS 傳輸
Client 端
先由 client 端開始配置,不然下 docker
指令時必須要帶一長串憑證參數,很麻煩。
使用 docker context 配置給 client 使用 (憑證路徑自行修改):
docker context create chi-tools-https \
--docker "host=tcp://chi-tools:2375,ca=/etc/docker/ssl/ca.crt,cert=/etc/docker/ssl/client.crt,key=/etc/docker/ssl/client.key" \
--description "Connect to chi-tools through https"
切換至配置好的 context:
docker context use chi-tools-https
docker context ls
Server 端
注意:一旦啟用 TLS Verify,就無法使用其他方式對 server 連線,包括 ssh。
修改 docker.service
中服務的啟動指令:
## Modify /usr/lib/systemd/system/docker.service
[Service]
ExecStart=/usr/bin/dockerd --tlsverify --tlscacert=/etc/docker/ssl/ca.crt --tlscert=/etc/docker/ssl/server.crt --tlskey=/etc/docker/ssl/server.key -H=0.0.0.0:2375
套用設定後重啟 docker:
sudo systemctl daemon-reload
sudo systemctl restart docker
systemctl status docker
檢驗成果
首先在 client 端下 docker version
應該要可以看到 client 及 server 的版本資訊。
接著抓一下封包檢查。
可以明顯看出有經過 TLS 加密囉~
Using SSH
另一種方式是改走 ssh 的方式。
首先要在 client 端配置 ssh key,並將其在 server 端信任。
ssh-keygen
ssh-copy-id chi@chi-tools
注意:這邊 ssh-copy-id
步驟的連線對象即為 docker server 的使用者以及主機名,分別有以下兩個注意事項:
1. 這邊的"使用者"必須存在於 docker server 中,並且備使用 docker.sock
的權限,比如說身為 root 或有包含在 docker 群組中。
2. "主機名"要求要能在 client 端能解析出 ip,比如說由 dns 解析,或是配置 /etc/hosts
。
接著建立 docker context。
docker context create chi-tools-ssh \
--docker host=ssh://chi@chi-tools \
--description "Connect to chi-tools through ssh"
切換至剛剛新增的 context。
docker context use chi-tools-ssh
docker context ls
接著再嘗試著抓包驗證一下效果吧~
可以明顯看出封包都變成 SSH 加密封包了,大功告成。
結語
最後來比較一下這兩種保護方式的差異吧~
SSH 連線
可以明顯看出配置的步驟比 https 方式容易許多,只要在 client 端產出 ssh key 後讓 server 端信任即可。
而且由於是使用了 server 端上 os 的 user 來呼叫 `docker.socket`,在管理上可以沿用 Linux 的 user 管理方式。
不過正因為如此,使用此種方式會對管理者的 OS 知識要求比較高一些,才能夠更安全的進行管理。
HTTPS 連線
雖然配置的過程非常繁瑣,但其實仔細檢視流程的話,server 端的配置只需要做一次即可。
剩下的部分就是當有新的 client 端使用者,只要向管理員申請憑證簽章,剩下的就是在 client 端使用憑證進行連線。過程中基本無須對 server 的 OS 進行操作。
而且還有一個好處,就是可以藉由憑證的有效時間對 client 進行管理。 (當然我也知道一般可能會想簽個 100 年比較省事)
比較
綜上所述,個人認為當團隊人員比較少的時候使用 ssh 進行連線會比較省事。但如果 client 的人數比較多的話,也許使用 https 的方式會比較容易追蹤管理。
最後,文章一開始提到的容器管理服務,目前大多數是以 https 的方式進行配置,由於 https 的保護無法與其他連線相容,所以如果有預期要使用的話,就別無選擇只能用 https 囉。