Nginx SSL快速雙向認證配置(腳本)

目前遇到一個項目有安全性要求,要求只有個別用戶有權限訪問。本着能用配置解決就毫不用代碼解決的原則,在Nginx上作一下限制和修改便可。html

這種需求其實實現方式不少,通過綜合評估考慮,以爲SSL雙向認證方案對用戶使用最簡單,遂決定用此方案。node

: 本方案在Ubuntu Server 16.04 LTS實施,其餘操做系統請酌情修改

SSL雙向認證

絕大多數SSL應用都以單向認證爲主,即客戶端只要信任服務端,就可使用服務端的公鑰加密後向服務端發起請求,由服務端的私鑰解密以後得到請求數據。nginx

若是這個過程反過來,讓服務端信任客戶端,服務端使用客戶端的公鑰加密以後將數據返回給客戶端,其實也是能夠作到的,原理和實現跟單向認證都差很少。git

服務端信任客戶端的操做每每也會伴隨着客戶端認證服務端的過程,因此讓服務端信任客戶端的SSL認證方式每每也被稱爲SSL雙向認證,而且要配置SSL雙向認證必須先開啓服務端SSL,先配置客戶端信任服務端。web

Nginx的SSL雙向認證配置

第一步 開啓https訪問

根據理論知識,咱們必須先開啓Nginx的SSL配置,即啓用https。這個過程較爲簡單,目前有let's encrypt這種免費的證書方案,不再用發愁本身搭建CA自簽了。申請免費證書的過程略過,直接貼啓用https的配置:瀏覽器

server {
  listen 80;
  listen 443 ssl http2;
  server_name example.com;

  ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
  # 只有Nginx >= 1.13.0 版本才支持TLSv1.3協議
  # ssl_protocols TLSv1.3;
  # Nginx低於1.13.0版本用這個配置
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_prefer_server_ciphers on; 
  ssl_dhparam dhparam.pem; # openssl dhparam -out /etc/nginx/dhparam.pem 4096
  ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
  ssl_ecdh_curve secp384r1; # Requires nginx >= 1.1.0
  ssl_session_timeout  10m;
  ssl_session_cache shared:SSL:10m;
  ssl_session_tickets off; # Requires nginx >= 1.5.9
  ssl_stapling on; # Requires nginx >= 1.3.7
  ssl_stapling_verify on; # Requires nginx => 1.3.7
  resolver 223.5.5.5 114.114.114.114 valid=300s;
  resolver_timeout 5s; 
  # 啓用HSTS的配置,若是你的域名下還有非標準端口訪問的http應用,請勿啓用HSTS
  # add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
  # 下面這個配置會拒絕Frame標籤內容,請確認你的網站沒有frame / iframe
  add_header X-Frame-Options DENY;
  add_header X-Content-Type-Options nosniff;
  add_header X-XSS-Protection "1; mode=block";

  # 爲了let's encrypt續期用,不用let's encrypt不須要這個location
  location /.well-known {
    root /usr/share/nginx/html;
  }

   ... SNIP ...

  # 強制http跳轉爲https
  if ($scheme != "https") {
    return 301 https://$http_host$request_uri;
  }
}
以上那一大堆ssl的配置參考來自於: https://cipherli.st/ 增強SSL的安全性配置

特別注意最後的強制https跳轉,咱們的目的是SSL雙向認證,不走https無任何意義,因此必須強制跳轉https。安全

第二步 生成客戶端證書並簽證(腳本)

這個過程詳細描述的文章太多了,這裏就不囉嗦介紹openssl和簽證過程了,本篇內容是快速生成雙向認證配置的證書,因此直接貼腳本就好了,命令都是參考互聯網上各類openssl雙向配置文檔,在此基礎之上進行了命令上的簡化與非交互式的支持。bash

整個目錄結構如圖:服務器

# tree /etc/nginx/ssl_certs/
/etc/nginx/ssl_certs/
├── create_ca_cert.sh
├── create_client_cert.sh
├── revoke_cert.sh

0 directories, 3 files

自行建立/etc/nginx/ssl_certs/,放入三個腳本,分別用於生成CA證書以及CA目錄(create_ca_cert.sh腳本的做用,只有第一次須要運行),建立客戶端證書,並用CA證書籤證(create_client_cert.sh腳本的做用,必須先生成CA證書),revoke_cert.sh腳本用於吊銷證書,須要收回權限的時候可使用。session

每一個腳本內容以下:

  • create_ca_cert.sh
#!/bin/bash -e

# 建立CA根證書
# 非交互式方式建立如下內容:
# 國家名(2個字母的代號)
C=CN
# 省
ST=Shannxi
# 市
L=Xian
# 公司名
O=My Company
# 組織或部門名
OU=技術部
# 服務器FQDN或頒發者名
CN=www.example.com
# 郵箱地址
emailAddress=admin@example.com

mkdir -p ./demoCA/{private,newcerts}
touch ./demoCA/index.txt
[ ! -f ./demoCA/seria ] && echo 01 > ./demoCA/serial
[ ! -f ./demoCA/crlnumber ] && echo 01 > ./demoCA/crlnumber
[ ! -f ./demoCA/cacert.pem ] && openssl req -utf8 -new -x509 -days 36500 -newkey rsa:2048 -nodes -keyout ./demoCA/private/cakey.pem -out ./demoCA/cacert.pem -subj "/C=${C}/ST=${ST}/L=${L}/O=${O}/OU=${OU}/CN=${CN}/emailAddress=${emailAddress}"
[ ! -f ./demoCA/private/ca.crl ] && openssl ca -crldays 36500 -gencrl -out "./demoCA/private/ca.crl"
  • create_client_cert.sh
#!/bin/bash -e

show_help() {
    echo "$0 [-h|-?|--help] [--ou ou] [--cn cn] [--email email]"
    echo "-h|-?|--help    顯示幫助"
    echo "--ou            設置組織或部門名,如: 技術部"
    echo "--cn            設置FQDN或全部者名,如: 馮宇"
    echo "--email         設置FQDN或全部者郵件,如: fengyu@example.com"
}

while [[ $# -gt 0 ]]
do
    case $1 in
        -h|-\?|--help)
            show_help
            exit 0
            ;;
        --ou)
            OU="${2}"
            shift
            ;;        
        --cn)
            CN="${2}"            
            shift
            ;;
        --email)
            emailAddress="${2}"            
            shift
            ;;
        --)
            shift
            break
        ;;
        *)
            echo -e "Error: $0 invalid option '$1'\nTry '$0 --help' for more information.\n" >&2
            exit 1
        ;;
    esac
shift
done

# 建立客戶端證書
# 非交互式方式建立如下內容:
# 國家名(2個字母的代號)
C=CN
# 省
ST=Shannxi
# 市
L=Xian
# 公司名
O=My Company
# 組織或部門名
OU=${OU:-測試部門}
# 服務器FQDN或授予者名
CN=${CN:-demo}
# 郵箱地址
emailAddress=${emailAddress:-demo@example.com}

mkdir -p "${CN}"

[ ! -f "${CN}/${CN}.key" ] && openssl req -utf8 -nodes -newkey rsa:2048 -keyout "${CN}/${CN}.key" -new -days 36500 -out "${CN}/${CN}.csr" -subj "/C=${C}/ST=${ST}/L=${L}/O=${O}/OU=${OU}/CN=${CN}/emailAddress=${emailAddress}"
[ ! -f "${CN}/${CN}.crt" ] && openssl ca -utf8 -batch -days 36500 -in "${CN}/${CN}.csr" -out "${CN}/${CN}.crt"
[ ! -f "${CN}/${CN}.p12" ] && openssl pkcs12 -export -clcerts -CApath ./demoCA/ -inkey "${CN}/${CN}.key" -in "${CN}/${CN}.crt" -certfile "./demoCA/cacert.pem" -passout pass: -out "${CN}/${CN}.p12"
  • revoke_cert.sh
#!/bin/bash -e

# 吊銷一個簽證過的證書

openssl ca -revoke "${1}/${1}.crt"
openssl ca -gencrl -out "./demoCA/private/ca.crl"

簡單分析一波腳本,首先是建立CA,對於Ubuntu系統來講,/etc/ssl/openssl.cnf配置中默認的CA路徑就是./demoCA,爲了避免改動默認配置,直接按照默認配置的內容建立這些目錄和文件便可。還有就是openssl子命令很是多,可是也和git同樣,能夠合併命令,好比用一條命令同時生成私鑰和簽證請求openssl req -nodes -newkey rsa:2048 -keyout client.key -new -out client.csr,在req的同時就作了genrsa。因爲建立CA腳本只是第一次運行須要,所以把證書配置直接寫死在腳本中就完事了。

接下來是建立客戶端證書,爲了簡化用戶的使用,在服務端幫助用戶生成證書並簽證,把簽證過的證書下發給用戶就能夠了。因爲用戶多是不一樣部門,不一樣姓名,不一樣郵件地址,所以將這三個參數外部化,作一下參數解析,加上友好的命令行提示防止遺忘。這個腳本特別注意最後一行,會生成一個PKCS12格式的證書。openssl默認產生的證書格式都是PEM的,會將公鑰和私鑰分開,可是瀏覽器導入的時候須要將這些內容合併起來造成證書鏈,因此須要將簽證過的證書和私鑰文件合併成一個PKCS12格式的證書,直接將這個.p12格式的證書交給用戶就能夠了。

最後是吊銷證書了,當但願收回某個用戶的訪問權限時,直接運行這個腳本跟上目錄名就能夠了。

接下來運行建立CA的腳本:

./create_ca_cert.sh
Generating a 2048 bit RSA private key
.......................+++
........................................................................................................+++
writing new private key to './demoCA/private/cakey.pem'
-----
Using configuration from /usr/ssl/openssl.cnf

此時產生的./demoCA目錄結構以下:

demoCA/
├── cacert.pem
├── crlnumber
├── crlnumber.old
├── index.txt
├── newcerts
├── private
│   ├── ca.crl
│   └── cakey.pem
└── serial

2 directories, 7 files

此時就能夠配置nginx了,在上面單向ssl的配置中,追加如下配置:

ssl_client_certificate ssl_certs/demoCA/cacert.pem;
  ssl_crl ssl_certs/demoCA/private/ca.crl;
  ssl_verify_client on;

ssl_client_certificate就是客戶端證書的CA證書了,表明此CA簽發的證書都是可信的,ssl_verify_client on;表明強制啓用客戶端認證,非法客戶端(無證書,證書不可信)都會返回400錯。

特別注意ssl_crl這個配置,表明Nginx會讀取一個CRL(Certificate Revoke List)文件,以前說過,可能會有收回用戶權限的需求,所以咱們必須有吊銷證書的功能,產生一個CRL文件讓Nginx知道哪些證書被吊銷了便可。

注意: Nginx配置都是靜態的,讀取配置文件以後都會加載到內存中,即便文件內容變化也不會從新讀取。所以當CRL文件發生變動以後,Nginx並不能意識到有新的證書被吊銷了,因此必須使用 reload指令讓Nginx從新讀取配置文件: service nginx reloadnginx -s reload

此時重啓Nginx服務,就能夠完成SSL雙向認證配置了。

咱們簽發一個證書看看:

./create_client_cert.sh --ou 財務部 --cn 財務經理 --email cy@example.com
Generating a 2048 bit RSA private key
................................+++
.............................................................................+++
writing new private key to '財務經理/財務經理.key'
-----
Using configuration from /usr/ssl/openssl.cnf
Check that the request matches the signature
Signature ok
Certificate Details:
        Serial Number: 1 (0x1)
        Validity
            Not Before: Jun 14 16:03:46 2018 GMT
            Not After : May 21 16:03:46 2118 GMT
        Subject:
            countryName               = CN
            stateOrProvinceName       = Shannxi
            organizationName          = My Company
            organizationalUnitName    = \U8D22\U52A1\U90E8
            commonName                = \U8D22\U52A1\U7ECF\U7406
            emailAddress              = cy@example.com
        X509v3 extensions:
            X509v3 Basic Constraints:
                CA:FALSE
            Netscape Comment:
                OpenSSL Generated Certificate
            X509v3 Subject Key Identifier:
                B5:91:0B:1F:FC:25:3B:2A:F9:EF:39:39:51:E3:1F:64:78:8A:C3:75
            X509v3 Authority Key Identifier:
                keyid:86:55:76:15:A3:F5:58:CB:8F:39:A3:56:8E:FF:18:97:AE:27:60:0F

Certificate is to be certified until May 21 16:03:46 2118 GMT (36500 days)

Write out database with 1 new entries
Data Base Updated

tree 財務經理/
財務經理/
├── 財務經理.crt
├── 財務經理.csr
├── 財務經理.key
└── 財務經理.p12

0 directories, 4 files

這個腳本生成了私鑰文件key,簽證請求文件csr,通過CA簽證後的證書文件crt(裏面沒有私鑰),以及將crt文件和key進行bundle以後的PKCS12格式的證書文件p12,將p12文件下載到本地,雙擊一路Next導入證書便可。

: 因爲CA的證書文件不會發生變化,所以簽證新的客戶端證書不須要restart或reload nginx

此次打開咱們的網站https://www.example.com,瀏覽器就會提示咱們選擇一個已有的客戶端證書進行認證了,沒問題就能夠看到網站內容了

: 每次導入新的證書以後,必須重啓瀏覽器才能提示使用新的證書文件

按照這種方式,有多少人須要受權,就能夠用這個腳本簽發多少個這樣的證書,用戶將p12證書導入本地就能夠正常訪問網站了。

當咱們須要收回某人的權限的時候(好比離職了),咱們須要吊銷他的證書:

./revoke_cert.sh 財務經理
Using configuration from /usr/ssl/openssl.cnf
Revoking Certificate 01.
Data Base Updated
Using configuration from /usr/ssl/openssl.cnf

service nginx reload

這個腳本會自動吊銷他的簽證文件crt,而且自動更新CRL文件。特別注意須要reload或restart nginx才能讓nginx從新加載CRL。這樣被吊銷的證書將沒法訪問網站了。

小結

本文咱們經過Nginx配置SSL雙向認證明現對客戶端的加密認證,咱們使用了簡易的腳本幫助咱們快速生成各類證書與簽證,免除記憶繁瑣openssl命令行,簡化使用。

固然這只是一個最小可用集,當規模比較大的時候可能須要作不少改進,好比加入CA的web ui,直接能夠操做簽證和吊銷證書,而且能夠自動重啓nginx。

再好比CRL這種靜態配置文件不適合你的場景,但願的動態更新吊銷證書列表,那麼能夠考慮OCSP方案,這個Nginx也是支持的,經過ssl_stapling_responder配置指定一個OCSP地址,這樣將不須要每次吊銷證書的時候都去重啓nginx了,openssl也提供了ocsp服務端的功能,這裏就不贅述了,能夠自行查找相關資料。

相關文章
相關標籤/搜索