目前遇到一个项目有安全性要求,要求只有个别用户有权限访问。本着能用配置解决就毫不用代码解决的原则,在Nginx上作一下限制和修改便可。html
这种需求其实实现方式不少,通过综合评估考虑,以为SSL双向认证方案对用户使用最简单,遂决定用此方案。node
注: 本方案在Ubuntu Server 16.04 LTS实施,其余操做系统请酌情修改
绝大多数SSL应用都以单向认证为主,即客户端只要信任服务端,就可使用服务端的公钥加密后向服务端发起请求,由服务端的私钥解密以后得到请求数据。nginx
若是这个过程反过来,让服务端信任客户端,服务端使用客户端的公钥加密以后将数据返回给客户端,其实也是能够作到的,原理和实现跟单向认证都差很少。git
服务端信任客户端的操做每每也会伴随着客户端认证服务端的过程,因此让服务端信任客户端的SSL认证方式每每也被称为SSL双向认证,而且要配置SSL双向认证必须先开启服务端SSL,先配置客户端信任服务端。web
根据理论知识,咱们必须先开启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 reload
或nginx -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服务端的功能,这里就不赘述了,能够自行查找相关资料。