详解Nginx SSL快速双向认证配置(脚本)
目前遇到一个项目有安全性要求,要求只有个别用户有权限访问。本着能用配置解决就绝不用代码解决的原则,在Nginx上做一下限制和修改即可。
这种需求其实实现方式很多,经过综合评估考虑,觉得SSL双向认证方案对用户使用最简单,遂决定用此方案。
注:本方案在UbuntuServer16.04LTS实施,其他操作系统请酌情修改
SSL双向认证
绝大多数SSL应用都以单向认证为主,即客户端只要信任服务端,就可以使用服务端的公钥加密后向服务端发起请求,由服务端的私钥解密之后获得请求数据。
如果这个过程反过来,让服务端信任客户端,服务端使用客户端的公钥加密之后将数据返回给客户端,其实也是可以做到的,原理和实现跟单向认证都差不多。
服务端信任客户端的操作往往也会伴随着客户端认证服务端的过程,所以让服务端信任客户端的SSL认证方式往往也被称为SSL双向认证,并且要配置SSL双向认证必须先开启服务端SSL,先配置客户端信任服务端。
Nginx的SSL双向认证配置
第一步开启https访问
根据理论知识,我们必须先开启Nginx的SSL配置,即启用https。这个过程较为简单,目前有let'sencrypt这种免费的证书方案,再也不用发愁自己搭建CA自签了。申请免费证书的过程略过,直接贴启用https的配置:
server{ listen80; listen443sslhttp2; server_nameexample.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_protocolsTLSv1.3; #Nginx低于1.13.0版本用这个配置 ssl_protocolsTLSv1TLSv1.1TLSv1.2; ssl_prefer_server_cipherson; ssl_dhparamdhparam.pem;#openssldhparam-out/etc/nginx/dhparam.pem4096 ssl_ciphers'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH'; ssl_ecdh_curvesecp384r1;#Requiresnginx>=1.1.0 ssl_session_timeout10m; ssl_session_cacheshared:SSL:10m; ssl_session_ticketsoff;#Requiresnginx>=1.5.9 ssl_staplingon;#Requiresnginx>=1.3.7 ssl_stapling_verifyon;#Requiresnginx=>1.3.7 resolver223.5.5.5114.114.114.114valid=300s; resolver_timeout5s; #启用HSTS的配置,如果你的域名下还有非标准端口访问的http应用,请勿启用HSTS #add_headerStrict-Transport-Security"max-age=63072000;includeSubDomains;preload"; #下面这个配置会拒绝Frame标签内容,请确认你的网站没有frame/iframe add_headerX-Frame-OptionsDENY; add_headerX-Content-Type-Optionsnosniff; add_headerX-XSS-Protection"1;mode=block"; #为了let'sencrypt续期用,不用let'sencrypt不需要这个location location/.well-known{ root/usr/share/nginx/html; } ...SNIP... #强制http跳转为https if($scheme!="https"){ return301https://$http_host$request_uri; } }
以上那一大堆ssl的配置参考来自于:https://cipherli.st/加强SSL的安全性配置
特别注意最后的强制https跳转,我们的目的是SSL双向认证,不走https无任何意义,所以必须强制跳转https。
第二步生成客户端证书并签证(脚本)
这个过程详细描述的文章太多了,这里就不啰嗦介绍openssl和签证过程了,本篇内容是快速生成双向认证配置的证书,所以直接贴脚本就行了,命令都是参考互联网上各种openssl双向配置文档,在此基础之上进行了命令上的简化与非交互式的支持。
整个目录结构如图:
#tree/etc/nginx/ssl_certs/ /etc/nginx/ssl_certs/ ├──create_ca_cert.sh ├──create_client_cert.sh ├──revoke_cert.sh 0directories,3files
自行创建/etc/nginx/ssl_certs/,放入三个脚本,分别用于生成CA证书以及CA目录(create_ca_cert.sh脚本的作用,只有第一次需要运行),创建客户端证书,并用CA证书签证(create_client_cert.sh脚本的作用,必须先生成CA证书),revoke_cert.sh脚本用于吊销证书,需要收回权限的时候可以使用。
每个脚本内容如下:
create_ca_cert.sh
#!/bin/bash-e #创建CA根证书 #非交互式方式创建以下内容: #国家名(2个字母的代号) C=CN #省 ST=Shannxi #市 L=Xian #公司名 O=MyCompany #组织或部门名 OU=技术部 #服务器FQDN或颁发者名 CN=www.example.com #邮箱地址 emailAddress=admin@example.com mkdir-p./demoCA/{private,newcerts} touch./demoCA/index.txt [!-f./demoCA/seria]&&echo01>./demoCA/serial [!-f./demoCA/crlnumber]&&echo01>./demoCA/crlnumber [!-f./demoCA/cacert.pem]&&opensslreq-utf8-new-x509-days36500-newkeyrsa: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]&&opensslca-crldays36500-gencrl-out"./demoCA/private/ca.crl"
create_client_cert.sh
#!/bin/bash-e show_help(){ echo"$0[-h|-?|--help][--ouou][--cncn][--emailemail]" echo"-h|-?|--help显示帮助" echo"--ou设置组织或部门名,如:技术部" echo"--cn设置FQDN或所有者名,如:冯宇" echo"--email设置FQDN或所有者邮件,如:fengyu@example.com" } while[[$#-gt0]] do case$1in -h|-\?|--help) show_help exit0 ;; --ou) OU="${2}" shift ;; --cn) CN="${2}" shift ;; --email) emailAddress="${2}" shift ;; --) shift break ;; *) echo-e"Error:$0invalidoption'$1'\nTry'$0--help'formoreinformation.\n">&2 exit1 ;; esac shift done #创建客户端证书 #非交互式方式创建以下内容: #国家名(2个字母的代号) C=CN #省 ST=Shannxi #市 L=Xian #公司名 O=MyCompany #组织或部门名 OU=${OU:-测试部门} #服务器FQDN或授予者名 CN=${CN:-demo} #邮箱地址 emailAddress=${emailAddress:-demo@example.com} mkdir-p"${CN}" [!-f"${CN}/${CN}.key"]&&opensslreq-utf8-nodes-newkeyrsa:2048-keyout"${CN}/${CN}.key"-new-days36500-out"${CN}/${CN}.csr"-subj"/C=${C}/ST=${ST}/L=${L}/O=${O}/OU=${OU}/CN=${CN}/emailAddress=${emailAddress}" [!-f"${CN}/${CN}.crt"]&&opensslca-utf8-batch-days36500-in"${CN}/${CN}.csr"-out"${CN}/${CN}.crt" [!-f"${CN}/${CN}.p12"]&&opensslpkcs12-export-clcerts-CApath./demoCA/-inkey"${CN}/${CN}.key"-in"${CN}/${CN}.crt"-certfile"./demoCA/cacert.pem"-passoutpass:-out"${CN}/${CN}.p12"
revoke_cert.sh
#!/bin/bash-e #吊销一个签证过的证书 opensslca-revoke"${1}/${1}.crt" opensslca-gencrl-out"./demoCA/private/ca.crl"
简单分析一波脚本,首先是创建CA,对于Ubuntu系统来说,/etc/ssl/openssl.cnf配置中默认的CA路径就是./demoCA,为了不改动默认配置,直接按照默认配置的内容创建这些目录和文件即可。还有就是openssl子命令非常多,但是也和git一样,可以合并命令,比如用一条命令同时生成私钥和签证请求opensslreq-nodes-newkeyrsa:2048-keyoutclient.key-new-outclient.csr,在req的同时就做了genrsa。由于创建CA脚本只是第一次运行需要,因此把证书配置直接写死在脚本中就完事了。
接下来是创建客户端证书,为了简化用户的使用,在服务端帮助用户生成证书并签证,把签证过的证书下发给用户就可以了。由于用户可能是不同部门,不同姓名,不同邮件地址,因此将这三个参数外部化,做一下参数解析,加上友好的命令行提示防止遗忘。这个脚本特别注意最后一行,会生成一个PKCS12格式的证书。openssl默认产生的证书格式都是PEM的,会将公钥和私钥分开,但是浏览器导入的时候需要将这些内容合并起来形成证书链,所以需要将签证过的证书和私钥文件合并成一个PKCS12格式的证书,直接将这个.p12格式的证书交给用户就可以了。
最后是吊销证书了,当希望收回某个用户的访问权限时,直接运行这个脚本跟上目录名就可以了。
接下来运行创建CA的脚本:
./create_ca_cert.sh
Generatinga2048bitRSAprivatekey .......................+++ ........................................................................................................+++ writingnewprivatekeyto'./demoCA/private/cakey.pem' ----- Usingconfigurationfrom/usr/ssl/openssl.cnf
此时产生的./demoCA目录结构如下:
demoCA/ ├──cacert.pem ├──crlnumber ├──crlnumber.old ├──index.txt ├──newcerts ├──private │├──ca.crl │└──cakey.pem └──serial 2directories,7files
此时就可以配置nginx了,在上面单向ssl的配置中,追加以下配置:
ssl_client_certificatessl_certs/demoCA/cacert.pem; ssl_crlssl_certs/demoCA/private/ca.crl; ssl_verify_clienton;
ssl_client_certificate就是客户端证书的CA证书了,代表此CA签发的证书都是可信的,ssl_verify_clienton;代表强制启用客户端认证,非法客户端(无证书,证书不可信)都会返回400错。
特别注意ssl_crl这个配置,代表Nginx会读取一个CRL(CertificateRevokeList)文件,之前说过,可能会有收回用户权限的需求,因此我们必须有吊销证书的功能,产生一个CRL文件让Nginx知道哪些证书被吊销了即可。
注意:Nginx配置都是静态的,读取配置文件之后都会加载到内存中,即使文件内容变化也不会重新读取。因此当CRL文件发生变更之后,Nginx并不能意识到有新的证书被吊销了,所以必须使用reload指令让Nginx重新读取配置文件:servicenginxreload或nginx-sreload
此时重启Nginx服务,就可以完成SSL双向认证配置了。
我们签发一个证书看看:
./create_client_cert.sh--ou财务部--cn财务经理--emailcy@example.com Generatinga2048bitRSAprivatekey ................................+++ .............................................................................+++ writingnewprivatekeyto'财务经理/财务经理.key' ----- Usingconfigurationfrom/usr/ssl/openssl.cnf Checkthattherequestmatchesthesignature Signatureok CertificateDetails: SerialNumber:1(0x1) Validity NotBefore:Jun1416:03:462018GMT NotAfter:May2116:03:462118GMT Subject: countryName=CN stateOrProvinceName=Shannxi organizationName=MyCompany organizationalUnitName=\U8D22\U52A1\U90E8 commonName=\U8D22\U52A1\U7ECF\U7406 emailAddress=cy@example.com X509v3extensions: X509v3BasicConstraints: CA:FALSE NetscapeComment: OpenSSLGeneratedCertificate X509v3SubjectKeyIdentifier: B5:91:0B:1F:FC:25:3B:2A:F9:EF:39:39:51:E3:1F:64:78:8A:C3:75 X509v3AuthorityKeyIdentifier: keyid:86:55:76:15:A3:F5:58:CB:8F:39:A3:56:8E:FF:18:97:AE:27:60:0F CertificateistobecertifieduntilMay2116:03:462118GMT(36500days) Writeoutdatabasewith1newentries DataBaseUpdated tree财务经理/ 财务经理/ ├──财务经理.crt ├──财务经理.csr ├──财务经理.key └──财务经理.p12 0directories,4files
这个脚本生成了私钥文件key,签证请求文件csr,经过CA签证后的证书文件crt(里面没有私钥),以及将crt文件和key进行bundle之后的PKCS12格式的证书文件p12,将p12文件下载到本地,双击一路Next导入证书即可。
注:由于CA的证书文件不会发生变化,因此签证新的客户端证书不需要restart或reloadnginx
这次打开我们的网站https://www.example.com,浏览器就会提示我们选择一个已有的客户端证书进行认证了,没问题就可以看到网站内容了
注:每次导入新的证书之后,必须重启浏览器才能提示使用新的证书文件
按照这种方式,有多少人需要授权,就可以用这个脚本签发多少个这样的证书,用户将p12证书导入本地就可以正常访问网站了。
当我们需要收回某人的权限的时候(比如离职了),我们需要吊销他的证书:
./revoke_cert.sh财务经理
Usingconfigurationfrom/usr/ssl/openssl.cnf RevokingCertificate01. DataBaseUpdated Usingconfigurationfrom/usr/ssl/openssl.cnf servicenginxreload
这个脚本会自动吊销他的签证文件crt,并且自动更新CRL文件。特别注意需要reload或restartnginx才能让nginx重新加载CRL。这样被吊销的证书将无法访问网站了。
小结
本文我们通过Nginx配置SSL双向认证实现对客户端的加密认证,我们使用了简易的脚本帮助我们快速生成各种证书与签证,免除记忆繁琐openssl命令行,简化使用。
当然这只是一个最小可用集,当规模比较大的时候可能需要做很多改进,比如加入CA的webui,直接可以操作签证和吊销证书,并且可以自动重启nginx。
再比如CRL这种静态配置文件不适合你的场景,希望的动态更新吊销证书列表,那么可以考虑OCSP方案,这个Nginx也是支持的,通过ssl_stapling_responder配置指定一个OCSP地址,这样将不需要每次吊销证书的时候都去重启nginx了,openssl也提供了ocsp服务端的功能,这里就不赘述了,可以自行查找相关资料。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。