爲 docker 中的 nginx 配置 https

沒有 https 加持的網站會逐漸地被瀏覽器標記爲不安全的,因此爲網站添加 https 已經變得刻不容緩。對於商業網站來講,花錢購買 SSL/TLS 證書並非什麼問題。但對於我的用戶來講,若是能有免費的 SSL/TLS 證書可用將會是很是幸福的事情!Let's Encrypt 就是一個提供免費 SSL/TLS 證書的網站,因爲其證書期限只有三個月,因此須要咱們用自動化的方式去更新證書。本文將介紹如何爲經過 docker 運行的 nginx 中的站點添加 https 支持,並自動完成證書的更新。本文的演示環境爲:運行在 Azure 上的 Ubuntu 16.04 主機(此圖來自互聯網):html

準備環境

在 Azure 上建立 Ubuntu 類型的虛機事件很是容易的事情,安裝 docker 也無須贅言。比較容易忽略的是配置合適的網絡安全組規則,好比打開 80 和 443 端口:node

還有就是配置 DNS:nginx

建立一個普通的 http 站點

簡單起見,直接使用一個鏡像中的 nodejs 應用做爲 web 站點:git

$ 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:github

$ mkdir -p nginx/{conf.d,conf.crt,html}
$ mkdir -p logs/{nginx,letsencrypt}

說明,本文演示的示例中須要咱們手動建立的文件和目錄結構以下:web

建立 nginx/nginx.conf 文件,內容以下:docker

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 在生成證書時須要用到的。最後讓咱們啓動容器(在用戶的家目錄下執行下面的命令):bash

$ 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="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

在用戶的家目錄中執行 ./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

相關文章
相關標籤/搜索