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驗證方法,選項是http,tls-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檔案裡,但無法通過埠驗證時可使用這種方法驗證
- http校驗
-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
androute53
. 還需要在/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
而不是dns
或tls-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.com和www.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,可謂十分良心
第二步 完成域名伺服器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
將對應內容替換到/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開啟),顯示如下介面即為正常
三 設定
1. 安全和密碼保護
可以使用nginx的htpasswd來對網站進行密碼保護,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. 證書相關
- 證書種類
- cert.pem,chain.pem,fullchain.pem和privkey.pem,其通過letsencrypt生成並由nginx的和其它各種應用使用
- privkey.pfx,Microsoft支援的格式,常用於Embnet Server等dotnet應用程式(無密碼)
- priv-fullchain-bundle.pem,一個捆綁私鑰和全鏈的pem證書,由ZNC等應用程式使用
- 在其他容器中使用證書
證書在容器中的存放在/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
- nginx-http-auth
- nginx-badbots
- 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的狀態,如下
Total banned表示歷史ban總記錄,在Banned IP list中可以看到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