1. 程式人生 > >Docker使用 linuxserver/letsencrypt 生成SSL證書最全解析及實踐

Docker使用 linuxserver/letsencrypt 生成SSL證書最全解析及實踐

本文使用http和dns兩種校驗方式對docker下linuxserver/letsencrypt 專案進行了實踐,並使用nginx的htpasswd來對網站進行密碼保護,並測試使用fail2ban防止htpasswd被暴力破解.全文基於linuxserver/letsencrypt 官方文件及其他官方資料並根據作者實踐進行詳細解析和記錄.

一 介紹

linuxserver/letsencrypt

這個容器設定了一個Nginx伺服器,支援php的反向代理和一個內建的letsencrypt客戶端,可以自動化生成或更新SSL伺服器證書.它還包含用於防禦入侵的fail2ban.

1 使用

docker create \
  --cap-add=NET_ADMIN \
  --name=letsencrypt \
  -v <path to data>:/config \
  -e PGID=<gid> -e PUID=<uid>  \
  -e EMAIL=<email> \
  -e URL=<url> \
  -e SUBDOMAINS=<subdomains> \
  -e VALIDATION=<method> \
  -p 80:80 -p 443:443 \
  -e TZ=<timezone> \
linuxserver/letsencrypt

2 引數

  • --cap-add=NET_ADMIN cap-add即Add Linux capabilities 新增linux核心能力,這裡具體新增的能力是允許執行網路管理任務,這是因為fail2ban需要修改iptables
  • -p 80 -p 443 - 埠
  • -v /config - 包括webroot在內的所有配置檔案都儲存在此處
  • -e URL - 頂級域名 (完全擁有則如:”customdomain.com” , 動態dns則如”customsubdomain.ddnsprovider.com” )
  • -e SUBDOMAINS - 證書覆蓋的子域名 (逗號分隔,無空格) .如 www,ftp,cloud
    .對於萬用字元證書, 請將此明確地設定為萬用字元 (萬用字元證書只允許通過dns方式驗證)
  • -e VALIDATION - letsencrypt驗證方法,選項是httptls-sni或者dns
    不同校驗方式的區別:

    • http校驗
      需要使用到80埠,故宿主機80埠應該轉發到容器的80埠.
    • tls-sni校驗
      需要使用到443埠,故宿主機443埠應該轉發到容器的443埠.
      注意:由於安全漏洞,letsencrypt禁用了tls-sni驗證,使用該方式會報錯:Client with the currently selected authenticator does not support any combination of challenges that will satisfy the CA.
    • dns驗證
      需要設定DNSPLUGIN變數(不是所有的DNS服務商都支援),並且需要在/config/dns-conf 資料夾下輸入憑據到相應的ini檔案裡,但無法通過埠驗證時可使用這種方法驗證
  • -e PGID 設定 GroupID

  • -e PUID 設定 UserID
    通過指定使用者ID和所屬群的ID來避免資料卷掛載(-v)時容器和宿主機直接可能產生的許可權問題.最好讓掛載的資料卷目錄的擁有者和指定的使用者統一.
    另外,需要注意,不能指定root使用者(即PGID=0,PUID=0),否則會一直報錯(但不影響使用)
#宿主機root使用者環境下使用例子(非官方,僅供參考)

#建立要掛載的目錄,此時該目錄屬root使用者和root組
mkdir /opt/letsencrypt
#建立docker使用者(預設會順帶新建同名Group)
useradd dockeruser
#修改資料夾歸屬(R代表遞迴操作,資料夾下的也一併修改)
chown -R dockeruser:dockeruser /opt/letsencrypt
#檢視dockeruser的使用者id和群id
id dockeruser
  • -e TZ - 時區 如 America/New_York
    注,上海時區為Asia/Shanghai

可選設定:

  • -e DNSPLUGIN - 如果 VALIDATION 設定為 dns則此項必選. 選項有 cloudflare, cloudxns, digitalocean, dnsimple, dnsmadeeasy, google, luadns, nsone, rfc2136 and route53. 還需要在/config/dns-conf 資料夾下輸入憑據到相應的ini檔案裡,這裡推薦使用cloudflare,免費而且好用.
    • 使用Cloudflare服務的話應確保設定為dns only而非dns + proxy(事實上Cloudflare的proxy已經提供免費自動SSL服務了,也就沒有本文的必要)
    • Google dns外掛的使用物件是企業付費產品”Google Cloud DNS”而非”Google Domains DNS”
  • -e EMAIL - 您的證書註冊和通知的電子郵件地址
  • -e DHLEVEL - dhparams位值(預設值= 2048,可設定為1024或4096)
  • -p 80 - VALIDATION設定為http而不是dnstls-sni時需要80埠進行轉發
  • -e ONLY_SUBDOMAINS - 僅為子域名獲取證書(主域名可能託管在另外一臺計算機且無法驗證)時請將此項設定為true
  • -e EXTRA_DOMAINS - 額外的完全限定域名(逗號分隔,無空格)如extradomain.com,subdomain.anotherdomain.org
  • -e STAGING - 設定為 true可以提高速率限制,但證書不會通過瀏覽器的安全測試,僅用於測試.
  • -e HTTPVAL - 已棄用, 請用VALIDATION 代替

二 實踐

使用http方式驗證

首先,你應該先保證要獲取證書的域名(子域名)能正確地訪問到主機,注意域名需要備案
這裡我對映的宿主機目錄為/opt/letsencrypt1,PGID和PUID有上文提到的方式獲得,配置的域名為my.comwww.my.com(實際上我配置的是另外一個我自己真正擁有的域名,這裡不貼出來)
注意使用http方式驗證的話開發80埠就可以了,這裡443埠也進行對映是為了證書獲取成功後可以通過使用https登入該容器提供的預設首頁進行確認

docker run -d \
--cap-add=NET_ADMIN \
--name=letsencrypt \
-v /opt/letsencrypt1:/config \
-e PGID=1002 -e PUID=1001  \
-e URL=my.com \
-e SUBDOMAINS=www \
-e VALIDATION=http \
-p 80:80 -p 443:443 \
-e TZ=Asia/Shanghai \
linuxserver/letsencrypt

容器會在後臺執行,這個時候應該提供如下指令檢視日誌輸出(CTRL+z退出)

docker logs -f letsencrypt

日誌輸出
最後我卡在Cleaning up challenges這一步,這是因為我域名沒有備案,無法通過域名訪問到我所在的主機,這個時候開啟域名連結被重定向到雲主機提供商的網頁禁止訪問,Let’s encrypt沒辦法通過域名訪問到本機,所以驗證失敗(事實上它也沒有說失敗,只是一直停在那裡)
網頁禁止訪問
毋庸置疑,我是因為原因一被禁止訪問的.

既然http(80埠)方式驗證走不通,tls-sni本來就不行,那就只能用dns驗證了

使用dns方式驗證

這裡以CloudFlare為例

第一步 完成域名伺服器配置

首先要有一個cloudflare賬號
然後在域名提供商那裡將域名的dns伺服器改成cloudflare提供的dns伺服器
然後在cloudflare那裡新增對應的解析記錄
注意解析記錄的Status那裡的圖示應該是灰色的,表示DNS only,如果亮了的話表示DNS and HTTP proay(CDN),要使用let’s encrypt的dns校驗的話就不要再開http代理和CDN了.開了代理的話cloudflare會免費給你提供(及自動維護更新)SSL證書,就可以直接https訪問了,不需要本文再幹嘛了,而且還有免費CDN,可謂十分良心

Cloudflare

第二步 完成域名伺服器API-KEY相關配置並啟動

這一步先正常啟動,會啟動失敗,但會生成所有的配置檔案,再根據相應的ini檔案裡的提示去域名伺服器提供商那裡找到相對應的憑證,修改ini檔案,重新啟動容器

啟動如下,這次我對映到宿主機目錄/opt/letsencrypt2下,把VALIDATION改為dns,增加DNSPLUGIN配置為cloudflare

docker run -d \
--cap-add=NET_ADMIN \
--name=letsencrypt \
-v /opt/letsencrypt2:/config \
-e PGID=1002 -e PUID=1001  \
-e URL=my.com \
-e SUBDOMAINS=www \
-e VALIDATION=dns \
-e DNSPLUGIN=cloudflare \
-p 80:80 -p 443:443 \
-e TZ=Asia/Shanghai \
linuxserver/letsencrypt

使用docker logs -f letsencrypt檢視
這次是在Cleaning up challenges之後報錯…錯誤提示也很明確,是Unknown X-Auth-Key or X-Auth-Email的問題,配置是在/config/dns-conf/cloudflare.ini這個檔案裡面

Cleaning up challenges
Error determining zone_id: 9103 Unknown X-Auth-Key or X-Auth-Email. Please confirm that you have supplied valid Cloudflare API credentials. (Did you enter the correct email address?)
IMPORTANT NOTES:
 - Your account credentials have been saved in your Certbot
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Certbot so
   making regular backups of this folder is ideal.
ERROR: Cert does not exist! Please see the validation error above. Make sure you entered correct credentials into the /config/dns-conf/cloudflare.ini file.

開啟/config/dns-conf/cloudflare.ini可以看到

# Instructions: https://github.com/certbot/certbot/blob/master/certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py#L20
# Replace with your values
dns_cloudflare_email = [email protected]
dns_cloudflare_api_key = 0123456789abcdef0123456789abcdef01234567

感興趣的可以到介紹的頁面去檢視相關資訊,也可以直接到對應域名解析服務提供商那裡去看
cloudflare檢視的地址是https://dash.cloudflare.com/profile,最上面是email,最下面是API Keys

Email

API Keys
將對應內容替換到/config/dns-conf/cloudflare.ini裡面(即宿主機的/opt/letsencrypt2/dns-conf/cloudflare.ini裡面)

然後使用docker rm -f letsencrypt強制刪掉原容器
再重新執行上面的docker run就可以成功啟動了
檢視日誌如下
成功啟動日誌

最終會停在Server ready這一行(如果用root使用者的uid和gid的話現在會一直報錯,但仍可使用),這個時候就可以用https打開了(內建的nginx只監聽443埠,所以不能用http開啟),顯示如下介面即為正常

https登入

三 設定

1. 安全和密碼保護

可以使用nginxhtpasswd來對網站進行密碼保護,htpasswd的相關用法可見htpasswd命令

  • 新增第一個密碼訪問使用者(-c引數表示建立一個加密檔案,如果原來有的話則把原來的刪掉)
docker exec -it letsencrypt htpasswd -c /config/nginx/.htpasswd <username>
  • 繼續新增密碼訪問使用者(把-c去掉即可)
docker exec -it letsencrypt htpasswd /config/nginx/.htpasswd <username>

如下為新增兩個使用者(lin和shen)
這裡寫圖片描述
檢視使用者資訊檔案(/opt/letsencrypt2是我對映到容器/config的目錄)
使用者資訊
然後,還需要在nginx的配置檔案(預設為/config/nginx/site-confs/default)裡面開啟auth_basic

    location / {
        try_files $uri $uri/ /index.html /index.php?$args =404;
        # 將下列兩行放到location{}裡面,**Restricted**是網站要求輸入賬號密碼時的提示語,後面是指定的使用者密碼檔案路徑
        auth_basic "Restricted";
        auth_basic_user_file /config/nginx/.htpasswd;
    }

最後要使用docker restart letsencrypt重新啟動容器使配置生效
登入網站,提示如下(我用的是firefox,不同瀏覽器可能顯示不一樣)
登入提示

2. 站點配置和反向代理

1. 預設配置檔案

預設的站點配置檔案位於/config/nginx/site-confs/default,可直接修改此檔案完成配置,也可將其他的conf檔案新增到此目錄.但如果將此default檔案刪除的話,容器啟動時對其重新建立.

2. 拒絕搜尋引擎抓取

如果不希望網站被搜尋引擎抓取,可以將以下命令新增到/config/nginx資料夾下的ssl.conf檔案中

add_header X-Robots-Tag "noindex, nofollow, nosnippet, noarchive";

3. 使用預設的配置檔案

本容器已經為熱門應用添加了預設的反向代理配置檔案,具體可以檢視/config/nginx/proxy_confs資料夾下的_readme檔案
/config/nginx/proxy_confs資料夾下的預設反向代理配置檔案有兩大類:

啟用預設的配置檔案:

  • 第一步: 確保在預設站點配置檔案(default檔案)的server項內包含以下命令:
include /config/nginx/proxy-confs/*.subfolder.conf;
include /config/nginx/proxy-confs/*.subdomain.conf;
  • 第二步: 重新命名conf檔案並刪除結尾的.sample
  • 第三步: 重啟letsencrypt容器

3. 證書相關

  1. 證書種類
    • cert.pemchain.pemfullchain.pemprivkey.pem,其通過letsencrypt生成並由nginx的和其它各種應用使用
    • privkey.pfx,Microsoft支援的格式,常用於Embnet Server等dotnet應用程式(無密碼)
    • priv-fullchain-bundle.pem,一個捆綁私鑰和全鏈的pem證書,由ZNC等應用程式使用
  2. 在其他容器中使用證書
    證書在容器中的存放在/config/etc/letsencrypt資料夾下,又因為/config資料夾被對映到宿主機,故如果需要在其他容器中使用,可以再把宿主機對應目錄下的etc/letsencrypt資料夾對映到需要用到證書的容器

4. fail2ban

fail2ban是一款實用軟體,可以監視你的系統日誌,然後匹配日誌的錯誤資訊(正則式匹配)執行相應的遮蔽動作。
多用於防止暴力破解和CC攻擊.

1. 檔案結構

/config/fail2ban目錄下主要有一個jail.local檔案和filter.d,action.d這兩個資料夾,另外還有一個fail2ban.sqlite3的資料庫檔案,這個不用管

  • jail.local檔案 : 負責fail2ban的主要配置,統管所有的jail的啟用和禁用和監控規,日誌路徑等等
  • filter.d資料夾 : 存放各個jail的過濾器配置檔案(如nginx-http-auth.conf檔案等)
  • action.d資料夾 : 存放各種功能對應的配置檔案(如sendmail.conf檔案等)

2. 使用說明

  • 該容器內建的fail2ban預設包括(並開啟)3個jail
    1. nginx-http-auth
    2. nginx-badbots
    3. nginx-botsearch
  • 可以通過修改檔案/config/fail2ban/jail.local去啟用或禁用其他jail
  • 要修改 filter.d資料夾action.d資料夾下的配置檔案時,不要直接編輯.conf檔案而應該建立一個同名的但以.local結尾的檔案(如想要修改nginx-http-auth.conf的話就建立一個nginx-http-auth.local).
    這是因為當actions和fileter更新時,.conf檔案會被覆寫而使修改失效,而.local檔案是以追加到.conf檔案後面的,不受.conf檔案的變動的影響.
    (根據對Dockerfile檔案的分析,這些.conf檔案應該是在構建docker映象時下載的,所以更新映象後即使複用原來的資料夾,.conf檔案也會被覆寫)
  • 檢視哪些jail是啟用的
docker exec -it letsencrypt fail2ban-client status
  • 檢視特定jail的狀態
docker exec -it letsencrypt fail2ban-client status <jail name>
  • 設定特定jail對特定ip放行(注意:linuxserver/letsencrypt 給的教程是沒有set的,會報指令錯誤,根據下面的fail2ban的官方命令我發現是要加set的)
docker exec -it letsencrypt fail2ban-client set <jail name> unbanip <IP>

3. 預設配置

檢視/config/fail2ban/jail.local檔案,部分內容如下

[DEFAULT]
# "bantime" is the number of seconds that a host is banned.
bantime  = 600
# A host is banned if it has generated "maxretry" during the last "findtime"
# seconds.
findtime  = 600
# "maxretry" is the number of failures before a host get banned.
maxretry = 5

[nginx-http-auth]

enabled  = true
filter   = nginx-http-auth
port     = http,https
logpath  = /config/log/nginx/error.log

上方[DEFAULT]的意思是 : 若在600秒內失敗5次則禁止訪問600秒
[nginx-http-auth]的內容是啟用,使用nginx-http-auth過濾器,監聽http和https埠並把日誌寫在/config/log/nginx/error.log檔案裡,為配置的選項則同[DEFAULT],更多配置資訊請看官方指南:http://www.fail2ban.org/wiki/index.php/MANUAL_0_8#General_settings

4. 測試

接下來當然是來測試一波啦
先確保網頁http密碼保護開啟,然後登陸,任意錯誤登陸(但使用者名稱不能為空)5次後網頁就開始提示找不到伺服器了

這個時候可以檢視一下jail的狀態,如下
jail
Total banned表示歷史ban總記錄,在Banned IP list中可以看到ip已經被封了
接下來再把ip解禁,如下
ip解禁
可以檢視/config/log/fail2ban/fail2ban.log檔案

這個是600秒後自動解禁的
2018-09-15 09:39:44,090 fail2ban.filter         [343]: INFO    [nginx-http-auth] Found 125.90.49.157 - 2018-09-15 09:39:44
2018-09-15 09:39:48,295 fail2ban.filter         [343]: INFO    [nginx-http-auth] Found 125.90.49.157 - 2018-09-15 09:39:48
2018-09-15 09:39:53,503 fail2ban.filter         [343]: INFO    [nginx-http-auth] Found 125.90.49.157 - 2018-09-15 09:39:53
2018-09-15 09:39:56,709 fail2ban.filter         [343]: INFO    [nginx-http-auth] Found 125.90.49.157 - 2018-09-15 09:39:56
2018-09-15 09:39:57,911 fail2ban.filter         [343]: INFO    [nginx-http-auth] Found 125.90.49.157 - 2018-09-15 09:39:57
2018-09-15 09:39:58,107 fail2ban.actions        [343]: NOTICE  [nginx-http-auth] Ban 125.90.49.157
2018-09-15 09:49:58,920 fail2ban.actions        [343]: NOTICE  [nginx-http-auth] Unban 125.90.49.157

這個是使用命令解禁的
2018-09-15 11:18:10,728 fail2ban.filter         [343]: INFO    [nginx-http-auth] Found 125.90.49.157 - 2018-09-15 11:18:10
2018-09-15 11:18:12,738 fail2ban.filter         [343]: INFO    [nginx-http-auth] Found 125.90.49.157 - 2018-09-15 11:18:12
2018-09-15 11:18:13,940 fail2ban.filter         [343]: INFO    [nginx-http-auth] Found 125.90.49.157 - 2018-09-15 11:18:13
2018-09-15 11:18:14,542 fail2ban.filter         [343]: INFO    [nginx-http-auth] Found 125.90.49.157 - 2018-09-15 11:18:14
2018-09-15 11:18:15,143 fail2ban.filter         [343]: INFO    [nginx-http-auth] Found 125.90.49.157 - 2018-09-15 11:18:14
2018-09-15 11:18:15,620 fail2ban.actions        [343]: NOTICE  [nginx-http-auth] Ban 125.90.49.157
2018-09-15 11:18:15,745 fail2ban.filter         [343]: INFO    [nginx-http-auth] Found 125.90.49.157 - 2018-09-15 11:18:15
2018-09-15 11:18:35,942 fail2ban.actions        [343]: NOTICE  [nginx-http-auth] Unban 125.90.49.157