沒(méi)有 https 加持的網(wǎng)站會(huì)逐漸地被瀏覽器標(biāo)記為不安全的,所以為網(wǎng)站添加 https 已經(jīng)變得刻不容緩。對(duì)于商業(yè)網(wǎng)站來(lái)說(shuō),花錢(qián)購(gòu)買(mǎi) SSL/TLS 證書(shū)并不是什么問(wèn)題。但對(duì)于個(gè)人用戶(hù)來(lái)說(shuō),如果能有免費(fèi)的 SSL/TLS 證書(shū)可用將會(huì)是非常幸福的事情!Let's Encrypt 就是一個(gè)提供免費(fèi) SSL/TLS 證書(shū)的網(wǎng)站,由于其證書(shū)期限只有三個(gè)月,所以需要我們用自動(dòng)化的方式去更新證書(shū)。本文將介紹如何為通過(guò) docker 運(yùn)行的 nginx 中的站點(diǎn)添加 https 支持,并自動(dòng)完成證書(shū)的更新。本文的演示環(huán)境為:運(yùn)行在 Azure 上的 Ubuntu 16.04 主機(jī)(此圖來(lái)自互聯(lián)網(wǎng)):
準(zhǔn)備環(huán)境
在 Azure 上創(chuàng)建 Ubuntu 類(lèi)型的虛機(jī)事件非常容易的事情,安裝 docker 也無(wú)須贅言。比較容易忽略的是配置合適的網(wǎng)絡(luò)安全組規(guī)則,比如打開(kāi) 80 和 443 端口:
還有就是配置 DNS:
創(chuàng)建一個(gè)普通的 http 站點(diǎn)
簡(jiǎn)單起見(jiàn),直接使用一個(gè)鏡像中的 nodejs 應(yīng)用作為 web 站點(diǎn):
$ docker pull ljfpower/nodedemo
$ docker network create -d bridge webnet
$ docker run -d --restart=always --expose=3000 \
--network=webnet --name=myweb \
ljfpower/nodedemo
在用戶(hù)的家目錄下創(chuàng)建 nginx 目錄及其子目錄 conf.d、conf.crt 和 html,創(chuàng)建 logs 目錄及其子目錄 nginx 和 letsencrypt:
$ mkdir -p nginx/{conf.d,conf.crt,html}
$ mkdir -p logs/{nginx,letsencrypt}
說(shuō)明,本文演示的示例中需要我們手動(dòng)創(chuàng)建的文件和目錄結(jié)構(gòu)如下:
創(chuàng)建 nginx/nginx.conf 文件,內(nèi)容如下:
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 2048;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
client_max_body_size 10M;
include /etc/nginx/conf.d/*.conf;
}
然后創(chuàng)建 nginx/conf.d/default.conf 文件,內(nèi)容如下:
upstream web{
server myweb:3000;
}
server {
listen 80;
listen [::]:80;
server_name filterinto.com www.filterinto.com;
location ^~ /.well-known/acme-challenge/ {
default_type "text/plain";
root /usr/share/nginx/html;
}
location = /.well-known/acme-challenge/ {
return 404;
}
location / {
proxy_pass http://web;
}
}
其中 /.well-known/acme-challenge/ 目錄是 certbot 工具在生成證書(shū)時(shí)創(chuàng)建的。接下來(lái)創(chuàng)建文件 nginx/html/index.html 文件,內(nèi)容如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Let's Encrypt First Time Cert Issue Site</title>
</head>
<body>
<h1>Hello HTTPS!</h1>
<p>
Just used for the very first time SSL certificates are issued by Let's Encrypt's
certbot.
</p>
</body>
</html>
這個(gè)頁(yè)面也是 certbot 在生成證書(shū)時(shí)需要用到的。最后讓我們啟動(dòng)容器(在用戶(hù)的家目錄下執(zhí)行下面的命令):
$ docker run -d \
-p 80:80 \
-v $(pwd)/nginx/conf.d:/etc/nginx/conf.d:ro \
-v $(pwd)/nginx/nginx.conf:/etc/nginx/nginx.conf:ro \
-v $(pwd)/logs/nginx:/var/log/nginx \
-v $(pwd)/nginx/html:/usr/share/nginx/html \
--restart=always \
--name=gateway \
--network=webnet \
nginx:1.14
注意:這時(shí)沒(méi)有映射 443 端口,也沒(méi)有掛載存放證書(shū)的目錄。只能以 http 協(xié)議訪(fǎng)問(wèn)訪(fǎng)問(wèn)我們的站點(diǎn):
為站點(diǎn)生成 SSL/TLS 證書(shū)
Let's Encrypt 是一個(gè)提供免費(fèi) SSL/TLS 證書(shū)的網(wǎng)站,它為用戶(hù)提供了 certbot 工具用來(lái)生成 SSL/TLS 證書(shū)。方便起見(jiàn),我們把 certbot 簡(jiǎn)單的封裝到容器中。在用戶(hù)的家目錄下創(chuàng)建 certbot 目錄,進(jìn)入 certbot 目錄并把下面的內(nèi)容保存到 Dockerfile 文件中:
FROM alpine:3.4
RUN apk add --update bash certbot
VOLUME ["/etc/letsencrypt"]
然后執(zhí)行下面的命令創(chuàng)建 certbot 鏡像:
$ docker build -t certbot:1.0 .
然后在 certbot 目錄下創(chuàng)建自動(dòng)更新證書(shū)的腳本 renew_cert.sh,內(nèi)容如下:
#!/bin/bash
WEBDIR="$1"
LIST=('filterinto.com' 'www.filterinto.com')
LED_LIST=()
WWW_ROOT=/usr/share/nginx/html
for domain in ${LIST[@]};do
docker run \
--rm \
-v ${WEBDIR}/nginx/conf.crt:/etc/letsencrypt \
-v ${WEBDIR}/logs/letsencrypt:/var/log/letsencrypt \
-v ${WEBDIR}/nginx/html:${WWW_ROOT} \
certbot:1.0 \
certbot certonly --verbose --noninteractive --quiet --agree-tos \
--webroot -w ${WWW_ROOT} \
--email="nick.li@grapecity.com" \
-d "$domain"
CODE=$?
if [ $CODE -ne 0 ]; then
FAILED_LIST+=($domain)
fi
done
# output failed domains
if [ ${#FAILED_LIST[@]} -ne 0 ];then
echo 'failed domain:'
for (( i=0; i<${#FAILED_LIST[@]}; i++ ));
do
echo ${FAILED_LIST[$i]}
done
fi
在用戶(hù)的家目錄中執(zhí)行 ./renew_cert.sh /home/nick 命令就可以生成新的證書(shū)(/home/nick 為當(dāng)前用戶(hù)的家目錄)。生成的證書(shū)被保存在 /home/nick/nginx/conf.crt/live 目錄下,以域名命名的目錄下保存著該域名的證書(shū):
然后去檢查下 nginx/html 目錄,發(fā)現(xiàn)多了一個(gè)隱藏的 .well-known 目錄,這個(gè)目錄就是在生成證書(shū)時(shí)創(chuàng)建的:
有了 SSL/TLS 證書(shū),接下來(lái)我們就可以配置 https 站點(diǎn)了。
為站點(diǎn)配置 SSL/TLS 證書(shū)
有了 SSL/TLS 證書(shū),接下來(lái)更新 nginx 的配置文件就可以了,更新 nginx/conf.d/default.conf 的內(nèi)容如下:
upstream web{
server myweb:3000;
}
server {
listen 80;
listen [::]:80;
server_name filterinto.com www.filterinto.com;
location ^~ /.well-known/acme-challenge/ {
default_type "text/plain";
root /usr/share/nginx/html;
}
location = /.well-known/acme-challenge/ {
return 404;
}
return 301 https://$server_name$request_uri;
}
server {
listen 443;
listen [::]:443;
server_name filterinto.com;
# enable ssl
ssl on;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH EDH+aRSA !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4";
# config ssl certificate
ssl_certificate conf.crt/live/filterinto.com/fullchain.pem;
ssl_certificate_key conf.crt/live/filterinto.com/privkey.pem;
location ^~ /.well-known/acme-challenge/ {
default_type "text/plain";
root /usr/share/nginx/html;
}
location = /.well-known/acme-challenge/ {
return 404;
}
location / {
proxy_pass http://web;
}
}
server {
listen 443;
listen [::]:443;
server_name www.filterinto.com;
# enable ssl
ssl on;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH EDH+aRSA !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4";
# config ssl certificate
ssl_certificate conf.crt/live/www.filterinto.com/fullchain.pem;
ssl_certificate_key conf.crt/live/www.filterinto.com/privkey.pem;
location ^~ /.well-known/acme-challenge/ {
default_type "text/plain";
root /usr/share/nginx/html;
}
location = /.well-known/acme-challenge/ {
return 404;
}
location / {
proxy_pass http://web;
}
}
然后刪除容器 gateway 并用下面的腳本重新創(chuàng)建:
$ docker run -d \
-p 80:80 \
-p 443:443 \
-v $(pwd)/nginx/conf.d:/etc/nginx/conf.d:ro \
-v $(pwd)/nginx/conf.crt:/etc/nginx/conf.crt:ro \
-v $(pwd)/nginx/nginx.conf:/etc/nginx/nginx.conf:ro \
-v $(pwd)/logs/nginx:/var/log/nginx \
-v $(pwd)/nginx/html:/usr/share/nginx/html \
--restart=always \
--name=gateway \
--network=webnet \
nginx:1.14
現(xiàn)在就只能通過(guò) https 來(lái)訪(fǎng)問(wèn)站點(diǎn)了:
自動(dòng)更新證書(shū)
Let's Encrypt 提供的 SSL/TLS 證書(shū)期限只有三個(gè)月,每過(guò)三個(gè)月要手動(dòng)更新一次證書(shū)也夠嗆的,下面我們介紹自動(dòng)更新證書(shū)的方法。
其實(shí)我們的配置已經(jīng)為自動(dòng)化更新證書(shū)提供了最大的便利(其實(shí)是使用 docker 帶來(lái)的便利),在定時(shí)任務(wù)中添加下面兩條記錄就可以了:
0 0 1 * * /home/nick/certbot/renew_cert.sh /home/nick >> /home/nick/logs/cert.log 2>> /home/nick/logs/cert.error.log
0 1 1 * * docker exec gateway nginx -s reload
每月 1 號(hào)的 0 點(diǎn)更新證書(shū),一個(gè)小時(shí)后 reload nginx 的配置。
總結(jié)
Let's Encrypt 是一個(gè)非常棒的網(wǎng)站,對(duì)于初學(xué)者和個(gè)人來(lái)說(shuō),能夠幫助我們輕松的實(shí)現(xiàn) HTTPS 站點(diǎn)(還是免費(fèi)的)!在方便的同時(shí),其隱患也是顯而易見(jiàn)的:既然誰(shuí)都可以無(wú)門(mén)檻的獲得 SSL/TLS 證書(shū),那么非法網(wǎng)站也可以通過(guò)它把自己偽裝成看上去合法的站點(diǎn)。 所以千萬(wàn)不要片面的認(rèn)為 HTTPS 站點(diǎn)就是安全的!
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。