為何需要 SSL 憑證 :

你是否有注意過網址有分成 httphttps 呢?

  • HTTP (HyperText Transfer Protocol)
  • HTTPS (HyperText Transfer Protocol Secure )

很明顯的,他們的差距就在於安全性,當你訪問 http 的網站時,瀏覽器會跳出非安全連線的提示,這是因為 http 封包是用明文傳遞資訊,很容易被截取,所以需要用 SSL/TLS 這樣的非對稱式加密來保證資訊安全,這些協議使用公開密鑰和私有密鑰來加密通信,確保只有發送方和接收方能夠解密和讀取數據。

不過這樣還不夠,瀏覽器只信任第三方機構(Certificate Authority,簡稱CA)發布的數位憑證,如果你使用了自簽名的憑證,瀏覽器仍會跳出非安全連線的提示。

那麼,為什麼瀏覽器只信任 CA 頒發的憑證呢?這是因為 CA 在頒發數位憑證前會驗證該網站的身份,他會核實網站所有者的身份和控制權,這樣可以有效防止中間人攻擊

什麼是 Let’s Encrypt :

如同前面提到的,我們需要向 CA 請求頒發數位憑證,而其中一間最知名又免費的 CA 機構就是 Let’s Encrypt,可以通過 ACME 協議自動化的完成證書申請和更新,且幾乎所有主要網頁瀏覽器和操作系統都信任 Let’s Encrypt 的證書。

可以參考官方列出的可相容系統

什麼是 Certbot :

Certbot 是一個免費且開源的工具,用於自動化管理 SSL/TLS 證書的申請、安裝、更新和配置。他是 Let’s Encrypt 計畫的官方客戶端,旨在簡化網站擁有者獲取和管理 HTTPS 加密的過程。

簡單來說,你可以透過 Certbot 來申請和更新 Let’s Encrypt 憑證。

Certbot 有提供很多做法來取得憑證:

作法 指令
全自動 (自備 HTTP 伺服器) – certbot
半自動(自備 HTTP 伺服器,不調整 HTTP 伺服器設定) – certbot certonly
webroot(自備 HTTP 伺服器,自行設定 acme-challenge 部分) – certbot certonly --webroot
手動(自備 HTTP 伺服器 、其他主機) – certbot certonly --manual
DNS 套件 – certbot certonly --dns-PLUGIN
Standalone(Certbot 提供獨立 HTTP 伺服器部分) – certbot certonly --standalone

什麼是 nginx :

nginx 是一個輕量級的反向代理伺服器,你可以用它來監聽你的指定 port,並將來源請求導向至你對應設定的地方。
例如你可以在 A機器 上設定一個 server 區塊來監聽 80 port + test.com,則所有打到 A機器 的 80 port 且 domain 為 test.com 的請求都會適用於這個區塊的設定,你可以做些不同的動作,然後導向至 A 機器其他地方抑或是其他 BCD 機器。

詳細教學會需要另外開一篇文章,此篇僅簡單概述。

使用教學 :

本次範例中我只會示範用 webroot 的做法,並且都採用 docker 來設定。

前置作業:

  • 你需要在你的機器上自行下載 docker 和 docker-compose。
  • 你需要有一個自己的域名,且 DNS 設定是對應到你當前要跑 certbot 的機器上。

以結論來說,我們會定義兩個容器,一個是 nginx,一個是 certbot,certbot 只有在取得和更新憑證時啟動,nginx 則是永遠保持運作並使用 ssl 憑證來轉向給背後指定的服務。

1. nginx.conf

首先,在你的專案根目錄新增一個 config 目錄,並在裡面新增一個 nginx.conf 後寫入以下內容:
有關 nginx 的目錄結構和語法就不多贅述了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# nginx worker process 的數量,通常設定為 CPU 的核心數量
worker_processes 1;

events {
worker_connections 512; # 每個 worker process 所能處理的連線數量
}

http {

client_max_body_size 1M; # 單次 request 超過指定用量則中斷連線
server_tokens off; # 關閉 nginx 的版本資訊

server {
listen 80; # listen port
listen [::]:80; # listen Ipv6 port
server_name your.domain.com # listen domain name,為了好理解才加.com,你要填你自己的

location /.well-known/acme-challenge/ { # Let's Encrypt 驗證用
allow all;
root /tmp/acme-challenge;
}

location / {
return 301 https://$host$request_uri; # 其餘的把 http 轉成 https
}
}

# 這裡是有憑證之後要導向指定服務的設定,但一開始還沒有憑證 nginx 會報錯所以先註解掉。
#server {
#listen 443 ssl;

#server_name your.domain.com;

#ssl_certificate /etc/letsencrypt/live/your.domain.com/fullchain.pem; # 指定伺服器所使用的 SSL/TLS 憑證
#ssl_certificate_key /etc/letsencrypt/live/your.domain.com/privkey.pem; # SSL/TLS 憑證的私鑰

#location / {
#proxy_pass https://ip.address # 指定服務的 ip
#proxy_set_header Host $host;
#proxy_set_header X-Real-IP $remote_addr;
#}
#}
}

2. Dockerfile

在你的 config 目錄下新增 Dockerfile 並寫入以下內容。

1
2
3
FROM nginx:1.24.0-alpine  # 使用 docker hub 的官方映像檔
RUN rm /etc/nginx/conf.d/default.conf # 刪除原本的預設檔
COPY ./nginx.conf /etc/nginx/nginx.conf # 將剛剛創建的 nginx.conf 檔案複製到容器對應的目錄

3. docker-compose.yml

回到你的專案根目錄,新增 docker-compose.yml 檔案並寫入以下內容 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
version: '3' # 不同版本有不同語法規範,請參考官網

services:
nginx:
container_name: "nginx_server"
build:
context: ./config # 設定用剛剛創建的 Dcokerfile 來啟動
ports:
- 80:80
- 443:443
volumes:
- ./config:/config
- /etc/letsencrypt:/etc/letsencrypt:ro # 等 certbot 拿完憑證會放這裡,所以要讓容器共用。
- /tmp/acme-challenge:/tmp/acme-challenge
restart: always

letsencrypt:
container_name: "certbot"
image: certbot/certbot:v2.6.0 # 指定用官方映像檔
# 拿 SSL 憑證,注意替換 domain 和 email
command: sh -c "certbot certonly --webroot -w /tmp/acme-challenge/
-d "your.domain.com" --text --agree-tos
--email your@email --rsa-key-size 4096 --verbose
--keep-until-expiring --preferred-challenges=http"
entrypoint: ""
volumes:
- "/etc/letsencrypt:/etc/letsencrypt" # 等 certbot 拿完憑證會放這裡,所以要讓容器共用。
- "/tmp/acme-challenge:/tmp/acme-challenge"
environment:
- TERM=xterm # 讓容器內的程式或命令知道如何與終端機互動,例如 vim 或變色等等。

4. 啟動 nginx 容器

CA 會打到 DNS 對應 IP 進行審核,所以要先啟動 nginx 容器。

1
docker-compose up --build -d nginx 

5. 啟動 certbot 容器來拿憑證

1
docker-compose up --build letsencrypt

6. 配置你的憑證檔案

回到你的 config/nginx.conf,把剛剛因為沒有憑證會報錯而註解掉的地方取消註解。

1
docker-compose up --build -d nginx

如此一來就大功告成了,之後要更新憑證就啟動 certbot 容器就好。

自動化腳本 :

作為懶人工程師當然要自動化啊,誰會想每次都記得手動跑更新!
到你的專案根目錄下新增 getSSL.sh 並寫入以下內容 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/bin/bash

# 設定專案路徑
PROJECT_PATH="/your/project/path"

# 設定日誌文件的路徑和名稱
LOG_FILE="$PROJECT_PATH/log/getSSL.log"

# fn:給訊息,寫入日誌文件
log_message() {
local message="$1"
local timestamp="$(date +'%Y-%m-%d %H:%M:%S')"

sudo echo "$timestamp: $message" >> "$LOG_FILE"
}

log_message "Starting letsencrypt container"

# 拿 SSL 憑證
sudo docker-compose run --rm letsencrypt >> "$LOG_FILE" 2>&1

# 有帶關鍵字就多設定排程
if [[ "$1" == "--schedule" ]]; then
log_message "Setting up crontab job"

# 設定腳本路徑和排程時間
CRON_SCHEDULE="0 0 * * 1"
CRON_FILENAME="getSSL.sh"
(crontab -l ; echo "$CRON_SCHEDULE cd $PROJECT_PATH; ./$CRON_FILENAME ") | sudo crontab -
exit 0
fi

log_message "Stopping letsencrypt container"

記得把檔案權限設為可執行,然後到專案目錄執行以下:

1
./getSSL.sh
1
./getSSL.sh --schedule

啊如果你是 windows 或其他有的沒有 OS 我也懶得一個一個找,就照這個概念去換語法就好。

參考