最近發現原本用的 Caddy Docker Image: abiosoft/caddy 已經沒有在維護了,就順手把 Caddy 從 v1 升級到 v2,並且使用官方提供的 image,也把之前覺得很難處理的 Cloudflare plugin 也處理好,這樣就能直接開啟 Cloudflare 的 proxy 功能隱藏真實 IP(雖然之前沒有開的話 87% 已經被記錄下來了 e.g. Shodan

ACME Challenges

這邊要講講不能直接開啟 Cloudflare 的 proxy(在 DNS 設定頁橘色雲的功能)功能的原因 — ACME Challenge。ACME Challenges 分為下面三種:

  • HTTP challenge (Default)
    • CA (e.g. Let’s Encrypt) 會造訪目標網域指向的 server 上的特殊目錄,如果有收到預期內容就會簽發憑證,但只支援 http (port 80),或是從 http 導向到 https (port 443)
  • TLS-ALPN challenge (Default)
    • CA 會使用 TLS 協議造訪 443 port,透過 handshake 交換特定的值,如果 CA 收到預期的值就會簽發憑證,較不適合一般開發者。
  • DNS challenge
    • 驗證 DNS 上的 txt 記錄是否有預期的內容。

以上三種都是為了確保簽發的憑證是交給有 domain 所有權的人,而不是交給其他惡意的使用者。因此像是 http challenge 不支援使用 80 port 以外的 port 做驗證,因為 > 1024 的 port 連一般的使用者都可以 listen。

再來看看 Cloudflare Proxy 關於 SSL/TLS 的設定:

cloudflare-ssl

如果選擇的是 Full 或是 Full (strict) 模式的話,Cloudflare 只會連接上 https 的 site,在使用 HTTP challenge 驗證時就會出事,因為這時候伺服器上並沒有可用的憑證。

所以解決方式如下:

  • 使用 DNS challenge 驗證,這樣就不用擔心 proxy 的問題。
  • 在尚未簽發憑證時,將 proxy mode 改為 Off(也可以是灰色雲模式)或是 Flexible,並在簽發憑證後改為 Full (strict),改回 Full (strict) 可以避免 infinite loop 的問題1,而之後 Caddy 也可以在 https 上更新憑證2
  • 將 proxy mode 改成 Full,並在 Caddy 使用自簽的憑證,簽發後可以改為 Full (strict)。

p.s. TLS-ALPN challenge 不能使用,因為在你的伺服器和 CA 之間隔著 Cloudflare proxy。

編譯有 Cloudflare Plugin 的 Caddy Binary

因為官方沒有提供帶有 cloudflare 功能的 Caddy docker image,因此這邊需要手動編譯出 binary 再轉成 image,Dockerfile 如下:

FROM caddy:builder AS builder
RUN xcaddy build --with github.com/caddy-dns/cloudflare

FROM caddy:latest
COPY --from=builder /usr/bin/caddy /usr/bin/caddy

ps. 這是 multi stage 的寫法,詳見:tachingchen.com

Caddyfile v1 to v2

在 v1 做 proxy 的 Caddyfile 可能會長下面這樣:

example.com {
  tls me@ujoj.cc
  gzip
  reverse_proxy / localhost:1234 {
    transparent
  }
}

根據 Caddy Docs 在 v2 有一些設定改變了:

  • proxy 變成 reverse_proxy 並且 reverse_proxy 會預設有 transparent 的效果
  • gzip 前要加上 encode
  • serving path 改變,如果只打 ‘/’ 會只處理 ‘/’ 這個 path,必須改為 ‘*’ 或是不打任何字元

上述 Caddyfile 要改成下面這樣:

example.com {
    tls me@ujoj.cc
    encode gzip
    reverse_proxy * localhost:1234
}

或是

example.com {
    tls me@ujoj.cc
    encode gzip
    reverse_proxy localhost:1234
}

TLS DNS Challenge

先在 https://dash.cloudflare.com/profile/api-tokens 取得 API Token,取得的設定可以參考我的: Cloudflare Token Settings

如果要使用前面提到的 DNS Challenge 的話,要再加上

tls {
    dns cloudflare {your_cloudflare_API_token}
}

會變成:

example.com {
    tls {
        dns cloudflare {your_cloudflare_API_token}
    }
    encode gzip
    reverse_proxy localhost:1234
}

或是全域設定成 Cloudflare DNS Challenge 的話:

{
    email me@ujoj.cc
    acme_dns cloudflare {your_cloudflare_API_token}
}
example.com {
    encode gzip
    reverse_proxy localhost:1234
}

Ref:


  1. 如果 proxy 設定成 Flexible,那使用者連上 https 時 Cloudflare 會試著連上 http,但簽證完成後,Caddy 會自動將 http 導向到 https,Now you get a loop :P 詳見:Cloudflare Docs Cloudflare Guide ↩︎

  2. 因為這時候伺服器上已經有有效憑證 ↩︎