轉載:http://seanlook.com/2015/05/28/nginx-ssl/
背景
項目組突然出現一起事故 ,所有的頁面無法訪問,崩潰了。。
經排查是證書問題
排查方法:
查看線上配置-證書所在文件
root@:/opt/nginx# cd ssl_certificate/
root@bq1:/opt/nginx/ssl_certificate# ls
atlasyun.net.crt pubu.im.key qiniu.com.key qiniu.io.key
atlasyun.net.key qiniu.com.crt qiniu.com.key.bak staticfile.org.crt
pubu.im.crt qiniu.com.crt.bak qiniu.io.crt staticfile.org.key
root@bq1:/opt/nginx/ssl_certificate#
root@bq1:/opt/nginx/conf# pwd
/opt/nginx/conf
root@bq1:/opt/nginx/conf# ls
fastcgi.conf init.sh mime.types scgi_params win-utf
fastcgi.conf.default koi-utf mime.types.default scgi_params.default
fastcgi_params koi-win nginx.conf uwsgi_params
fastcgi_params.default _md5 nginx.conf.default uwsgi_params.default
root@bq1:/opt/nginx/conf# cat nginx.conf
user nobody nogroup;
error_log logs/error.log;
pid logs/nginx.pid;
worker_processes auto;
worker_rlimit_nofile 10240;
events {
use epoll;
worker_connections 10240;
}
http {
include mime.types;
default_type application/octet-stream;
# builtin server
server {
listen 127.0.0.1:80;
server_name localhost;
location /nginxstatus {
access_log off;
stub_status on;
allow 127.0.0.1;
deny all;
}
}
# default http log configs
log_format main '$remote_addr\t$remote_user\t[$time_local]\t"$request"\t'
'$request_length\t$status\t$bytes_sent\t$body_bytes_sent\t'
'"$http_referer"\t"$http_user_agent"\t"$http_x_forwarded_for"\t'
'$upstream_addr\t$host\t$upstream_http_x_reqid\t$upstream_response_time\t'
'$request_time\t$http_x_from_cdn\t$upstream_cache_status\t'
'$upstream_http_x_k_appid\t$upstream_http_x_from_pod\t$upstream_http_x_qiniu_root_cause';
access_log logs/access.log main;
# default http limit conn configs
limit_conn_zone $http_host zone=servicelimit:10m;
limit_conn_zone $http_host zone=limitspeed:50m;
limit_conn_log_level error;
limit_req_zone $binary_remote_addr zone=one:50m rate=2r/s;
# default http ssl configs
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;
ssl_prefer_server_ciphers on;
# default http gzip configs
gzip on;
gzip_types text/plain text/css text/javascript text/xml application/x-javascript application/json application/xml application/xml+rss application/javascript;
# default http core configs
# See http://nginx.org/en/docs/http/ngx_http_core_module.html.
client_max_body_size 1024m;
client_body_buffer_size 512K;
client_body_temp_path /disk1/nginx 3 2;
sendfile on;
tcp_nopush off;
keepalive_timeout 65s;
# default http proxy configs
# See http://nginx.org/en/docs/http/ngx_http_proxy_module.html.
proxy_buffers 64 4k;
proxy_buffer_size 32k;
# include extra configs (relative to prefix/conf/nginx.conf)
include /opt/nginx/sites-enabled/qbox.me/*.conf;
include /opt/nginx/sites-enabled/qcos.net/*.conf;
}
root@bq1:/opt/nginx/conf#
於是乎,打算學習一番,找到了這篇文章,很贊 ��
“nginx配置ssl加密(單雙向認證、部分https)“
nginx下配置ssl本來是很簡單的,無論是去認證中心買SSL安全證書還是自簽署證書,但最近公司OA的一個需求,得以有個機會實際折騰一番。一開始採用的是全站加密,所有訪問http:80的請求強制轉換(rewrite)到https,後來自動化測試結果說響應速度太慢,https比http慢慢30倍,心想怎麼可能,鬼知道他們怎麼測的。所以就試了一下部分頁面https(不能只針對某類動態請求才加密)和雙向認證。下面分節介紹。
默認nginx是沒有安裝ssl模塊的,需要編譯安裝nginx時加入–with-http_ssl_module選項。
關於SSL/TLS原理請參考 這裏,如果你只是想測試或者自簽發ssl證書,參考 這裏 。
提示:nignx到後端服務器由於一般是內網,所以不加密。
1. 全站ssl
全站做ssl是最常見的一個使用場景,默認端口443,而且一般是單向認證。
server {
listen 443;
server_name example.com;
root /apps/www;
index index.html index.htm;
ssl on;
ssl_certificate ../SSL/ittest.pem;
ssl_certificate_key ../SSL/ittest.key;
# ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
# ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
# ssl_prefer_server_ciphers on;
}
如果想把http的請求強制轉到https的話:
server {
listen 80;
server_name example.me;
rewrite ^ https://$server_name$request_uri? permanent;
### 使用return的效率會更高
# return 301 https://$server_name$request_uri;
}
ssl_certificate證書其實是個公鑰,它會被髮送到連接服務器的每個客戶端,ssl_certificate_key私鑰是用來解密的,所以它的權限要得到保護但nginx的主進程能夠讀取。當然私鑰和證書可以放在一個證書文件中,這種方式也只有公鑰證書才發送到client。
ssl_protocols指令用於啓動特定的加密協議,nginx在1.1.13和1.0.12版本後默認是ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2,TLSv1.1與TLSv1.2要確保OpenSSL >= 1.0.1 ,SSLv3 現在還有很多地方在用但有不少被攻擊的漏洞。
ssl_ciphers選擇加密套件,不同的瀏覽器所支持的套件(和順序)可能會不同。這裏指定的是OpenSSL庫能夠識別的寫法,你可以通過 openssl -v cipher ‘RC4:HIGH:!aNULL:!MD5’(後面是你所指定的套件加密算法) 來看所支持算法。
ssl_prefer_server_ciphers on設置協商加密算法時,優先使用我們服務端的加密套件,而不是客戶端瀏覽器的加密套件。
https優化參數
ssl_session_cache shared:SSL:10m; : 設置ssl/tls會話緩存的類型和大小。如果設置了這個參數一般是shared,buildin可能會參數內存碎片,默認是none,和off差不多,停用緩存。如shared:SSL:10m表示我所有的nginx工作進程共享ssl會話緩存,官網介紹說1M可以存放約4000個sessions。 詳細參考serverfault上的問答ssl_session_cache。
ssl_session_timeout : 客戶端可以重用會話緩存中ssl參數的過期時間,內網系統默認5分鐘太短了,可以設成30m即30分鐘甚至4h。
設置較長的keepalive_timeout也可以減少請求ssl會話協商的開銷,但同時得考慮線程的併發數了。
提示:在生成證書請求csr文件時,如果輸入了密碼,nginx每次啓動時都會提示輸入這個密碼,可以使用私鑰來生成解密後的key來代替,效果是一樣的,達到免密碼重啓的效果:
1
openssl rsa -in ittest.key -out ittest_unsecure.key
導入證書
如果你是找一個知名的ssl證書頒發機構如VeriSign、Wosign、StartSSL簽發的證書,瀏覽器已經內置並信任了這些根證書,如果你是自建C或獲得二級CA授權,都需要將CA證書添加到瀏覽器,這樣在訪問站點時纔不會顯示不安全連接。各個瀏覽的添加方法不在本文探討範圍內。
2. 部分頁面ssl
一個站點並不是所有信息都是非常機密的,如網上商城,一般的商品瀏覽可以不通過https,而用戶登錄以及支付的時候就強制經過https傳輸,這樣用戶訪問速度和安全性都得到兼顧。
但是請注意不要理解錯了,是對頁面加密而不能針對某個請求加密,一個頁面或地址欄的URL一般會發起許多請求的,包括css/png/js等靜態文件和動態的java或php請求,所以要加密的內容包含頁面內的其它資源文件,否則就會出現http與https內容混合的問題。在http頁面混有https內容時,頁面排版不會發生亂排現象;在https頁面中包含以http方式引入的圖片、js等資源時,瀏覽器爲了安全起見會阻止加載。
下面是隻對example.com/account/login登錄頁面進行加密的栗子:
root /apps/www;
index index.html index.htm;
server {
listen 80;
server_name example.com;
location ^~ /account/login {
rewrite ^ https://$server_name:443$request_uri? permanent;
}
location / {
proxy_pass http://localhost:8080;
### Set headers ####
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
}
}
server {
listen 443 ssl;
server_name example.com;
ssl on;
ssl_certificate ../SSL/ittest.pem;
ssl_certificate_key ../SSL/ittest.key;
ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
ssl_prefer_server_ciphers on;
location ^~ /account/login {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
### Most PHP, Python, Rails, Java App can use this header -> https ###
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
rewrite ^ http://$server_name$request_uri? permanent;
}
}
關於rewrite與location的寫法參考這裏。當瀏覽器訪問http://example.com/account/login.xx時,被301到https://example.com/account/login.xx,在這個ssl加密的虛擬主機裏也匹配到/account/login,反向代理到後端服務器,後面的傳輸過程是沒有https的。這個login.xx頁面下的其它資源也是經過https請求nginx的,登錄成功後跳轉到首頁時的鏈接使用http,這個可能需要開發代碼裏面控制。
上面配置中使用了proxy_set_header X-Forwarded-Proto
ssl配置塊還有個與不加密的80端口類似的location /,它的作用是當用戶直接通過https訪問首頁時,自動跳轉到不加密端口,你可以去掉它允許用戶這樣做。
3. 實現雙向ssl認證
上面的兩種配置都是去認證被訪問的站點域名是否真實可信,並對傳輸過程加密,但服務器端並沒有認證客戶端是否可信。(實際上除非特別重要的場景,也沒必要去認證訪問者,除非像銀行U盾這樣的情況)
要實現雙向認證HTTPS,nginx服務器上必須導入CA證書(根證書/中間級證書),因爲現在是由服務器端通過CA去驗證客戶端的信息。還有必須在申請服務器證書的同時,用同樣的方法生成客戶證書。取得客戶證書後,還要將它轉換成瀏覽器識別的格式(大部分瀏覽器都認識PKCS12格式):
1
openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12
然後把這個client.p12發給你相信的人,讓它導入到瀏覽器中,訪問站點建立連接的時候nginx會要求客戶端把這個證書發給自己驗證,如果沒有這個證書就拒絕訪問。
同時別忘了在 nginx.conf 裏配置信任的CA:(如果是二級CA,請把根CA放在後面,形成CA證書鏈)
proxy_ignore_client_abort on;
ssl on;
...
ssl_verify_client on;
ssl_verify_depth 2;
ssl_client_certificate ../SSL/ca-chain.pem;
# 在雙向location下加入:
proxy_set_header X-SSL-Client-Cert $ssl_client_cert;
拓展:使用geo模塊
nginx默認安裝了一個ngx_http_geo_module,這個geo模塊可以根據客戶端IP來創建變量的值,用在如來自172.29.73.0/24段的IP訪問login時使用雙向認證,其它段使用一般的單向認證。
geo $duplexing_user {
default 1;
include geo.conf; # 注意在0.6.7版本以後,include是相對於nginx.conf所在目錄而言的
}
語法 geo [
127.0.0.1/32 LOCAL; # 本地
172.29.73.23/32 SEAN; # 某個IP
172.29.73.0/24 1; # IP段,可以按國家或地域定義後面的不同的值
需要配置另外一個虛擬主機server{ssl 445},裏面使用上面雙向認證的寫法,然後在80或443裏使用變量$duplexing_user去判斷,如果爲1就rewrite到445,否則rewrite到443。具體用法可以參考nginx geo使用方法。