為 docker 中的 nginx 配置 https
沒有 https 加持的網站會逐漸地被瀏覽器標記為不安全的,所以為網站添加 https 已經變得刻不容緩。對於商業網站來說,花錢購買 SSL/TLS 證書並不是什麽問題。但對於個人用戶來說,如果能有免費的 SSL/TLS 證書可用將會是非常幸福的事情!Let‘s Encrypt 就是一個提供免費 SSL/TLS 證書的網站,由於其證書期限只有三個月,所以需要我們用自動化的方式去更新證書。本文將介紹如何為通過 docker 運行的 nginx 中的站點添加 https 支持,並自動完成證書的更新。本文的演示環境為:運行在 Azure 上的 Ubuntu 16.04 主機(此圖來自互聯網):
準備環境
在 Azure 上創建 Ubuntu 類型的虛機事件非常容易的事情,安裝 docker 也無須贅言。比較容易忽略的是配置合適的網絡安全組規則,比如打開 80 和 443 端口:
還有就是配置 DNS:
創建一個普通的 http 站點
簡單起見,直接使用一個鏡像中的 nodejs 應用作為 web 站點:
$ docker pull ljfpower/nodedemo $ docker network create -d bridge webnet $ docker run -d --restart=always --expose=3000 --network=webnet --name=myweb ljfpower/nodedemo
在用戶的家目錄下創建 nginx 目錄及其子目錄 conf.d、conf.crt 和 html,創建 logs 目錄及其子目錄 nginx 和 letsencrypt:
$ mkdir -p nginx/{conf.d,conf.crt,html} $ mkdir -p logs/{nginx,letsencrypt}
說明,本文演示的示例中需要我們手動創建的文件和目錄結構如下:
創建 nginx/nginx.conf 文件,內容如下:
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; }
然後創建 nginx/conf.d/default.conf 文件,內容如下:
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 工具在生成證書時創建的。接下來創建文件 nginx/html/index.html 文件,內容如下:
<!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>
這個頁面也是 certbot 在生成證書時需要用到的。最後讓我們啟動容器(在用戶的家目錄下執行下面的命令):
$ 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
註意:這時沒有映射 443 端口,也沒有掛載存放證書的目錄。只能以 http 協議訪問訪問我們的站點:
為站點生成 SSL/TLS 證書
Let‘s Encrypt 是一個提供免費 SSL/TLS 證書的網站,它為用戶提供了 certbot 工具用來生成 SSL/TLS 證書。方便起見,我們把 certbot 簡單的封裝到容器中。在用戶的家目錄下創建 certbot 目錄,進入 certbot 目錄並把下面的內容保存到 Dockerfile 文件中:
FROM alpine:3.4 RUN apk add --update bash certbot VOLUME ["/etc/letsencrypt"]
然後執行下面的命令創建 certbot 鏡像:
$ docker build -t certbot:1.0 .
然後在 certbot 目錄下創建自動更新證書的腳本 renew_cert.sh,內容如下:
#!/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="[email protected]" -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
在用戶的家目錄中執行 ./renew_cert.sh /home/nick 命令就可以生成新的證書(/home/nick 為當前用戶的家目錄)。生成的證書被保存在 /home/nick/nginx/conf.crt/live 目錄下,以域名命名的目錄下保存著該域名的證書:
然後去檢查下 nginx/html 目錄,發現多了一個隱藏的 .well-known 目錄,這個目錄就是在生成證書時創建的:
有了 SSL/TLS 證書,接下來我們就可以配置 https 站點了。
為站點配置 SSL/TLS 證書
有了 SSL/TLS 證書,接下來更新 nginx 的配置文件就可以了,更新 nginx/conf.d/default.conf 的內容如下:
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 並用下面的腳本重新創建:
$ 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
現在就只能通過 https 來訪問站點了:
自動更新證書
Let‘s Encrypt 提供的 SSL/TLS 證書期限只有三個月,每過三個月要手動更新一次證書也夠嗆的,下面我們介紹自動更新證書的方法。
其實我們的配置已經為自動化更新證書提供了最大的便利(其實是使用 docker 帶來的便利),在定時任務中添加下面兩條記錄就可以了:
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 號的 0 點更新證書,一個小時後 reload nginx 的配置。
總結
Let‘s Encrypt 是一個非常棒的網站,對於初學者和個人來說,能夠幫助我們輕松的實現 HTTPS 站點(還是免費的)!在方便的同時,其隱患也是顯而易見的:既然誰都可以無門檻的獲得 SSL/TLS 證書,那麽非法網站也可以通過它把自己偽裝成看上去合法的站點。 所以千萬不要片面的認為 HTTPS 站點就是安全的!
參考:
Setting up HTTPS on Nginx using Let’s Encrypt
在 docker nginx 下使用 docker let‘s encrypt
How to Set Up Free SSL Certificates from Let‘s Encrypt using Docker and Nginx
為 docker 中的 nginx 配置 https