美文网首页DTeam团队日志
Nginx SSL快速双向认证配置(脚本)

Nginx SSL快速双向认证配置(脚本)

作者: 冯宇Ops | 来源:发表于2018-06-15 00:28 被阅读71次

    目前遇到一个项目有安全性要求,要求只有个别用户有权限访问。本着能用配置解决就绝不用代码解决的原则,在Nginx上做一下限制和修改即可。

    这种需求其实实现方式很多,经过综合评估考虑,觉得SSL双向认证方案对用户使用最简单,遂决定用此方案。

    : 本方案在Ubuntu Server 16.04 LTS实施,其他操作系统请酌情修改

    SSL双向认证

    绝大多数SSL应用都以单向认证为主,即客户端只要信任服务端,就可以使用服务端的公钥加密后向服务端发起请求,由服务端的私钥解密之后获得请求数据。

    如果这个过程反过来,让服务端信任客户端,服务端使用客户端的公钥加密之后将数据返回给客户端,其实也是可以做到的,原理和实现跟单向认证都差不多。

    服务端信任客户端的操作往往也会伴随着客户端认证服务端的过程,所以让服务端信任客户端的SSL认证方式往往也被称为SSL双向认证,并且要配置SSL双向认证必须先开启服务端SSL,先配置客户端信任服务端。

    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双向配置文档,在此基础之上进行了命令上的简化与非交互式的支持。

    整个目录结构如图:

    # 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脚本用于吊销证书,需要收回权限的时候可以使用。

    每个脚本内容如下:

    • 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服务端的功能,这里就不赘述了,可以自行查找相关资料。

    相关文章

      网友评论

      • Mark_Du:哟,同校同专业的。。。
        nginx ssl部署有个问题,一直想不明白,请教一下。。。
        nginx部署了ssl后,外部访问都直接到nginx,nginx再转到后面的服务,后面的服务就不用部署ssl了。。。那问题来了,如果后面的服务,要发一个ssl连接请求到别的服务器,怎么办呢?
        冯宇Ops:如果是单向ssl的话,客户端是不需要管ssl证书问题的(最多配置一下非可信CA签发的证书是否信赖的配置就够了)。如果你请求的服务端要求必须ssl双向认证,那么你的客户端必须配置一个服务端信任的CA机构签发的证书,比如curl就有-E参数提供这个证书,别的http client也有类似的功能。

      本文标题:Nginx SSL快速双向认证配置(脚本)

      本文链接:https://www.haomeiwen.com/subject/ofiueftx.html