Lan Tian @ Blog

nginx 配置 LDAP 认证

我的各台服务器上安装了各种不同的服务,都有各自的用户名密码体系,难以统一管理。假设未来某天我的密码泄露了,一个个修改就会非常累人。因此,我希望用一个服务来专门管理用户名密码,其它服务都从它上面获取认证信息。

LDAP 是常用的认证协议之一,不仅有许多软件原生支持它的认证(包括 Jenkins,pfSense 等),而且通过插件可以使得 nginx 支持它,为任何基于网页的服务加上统一管理的认证。

添加插件

如果你的 nginx 已经是源代码编译的,添加 nginx 的 LDAP 插件只需要三步:

  1. apk add openldap-dev
  2. git clone https://github.com/kvspb/nginx-auth-ldap.git
  3. ./configure --add-module=/path/to/nginx-auth-ldap

我依然使用 Docker 部署 nginx,Dockerfile 可以在 https://github.com/xddxdd/dockerfiles/blob/master/nginx/Dockerfile.amd64 找到,其中的编译参数可以参考。

配置认证

安装插件后,先在 nginx.conf 的 http 配置块中添加 ldap_server 配置块。为了防止我的单台服务器出问题导致认证服务全挂,我暂时先用了 JumpCloud 的 LDAP 服务,配置如下:

ldap_server jumpcloud {
    url ldap://ldap.jumpcloud.com/ou=Users,o=[你的 JumpCloud LDAP 编号],dc=jumpcloud,dc=com?uid?sub?(objectClass=posixAccount);
    binddn "uid=[LDAP 认证专用用户名],ou=Users,o=[你的 JumpCloud LDAP 编号],dc=jumpcloud,dc=com";
    binddn_passwd "[LDAP 认证专用用户密码]";
    group_attribute "memberOf";
    group_attribute_is_dn on;

    max_down_retries 3;
    connections 1;
    referral off;

    require valid_user;
    satisfy any;
}

然后在要保护的 server 块或 location 块中添加如下内容:

location /private {
    auth_ldap "Forbidden";
    auth_ldap_servers jumpcloud;
}

即可使用指定的 LDAP 服务器进行认证。

不过要注意的是,LDAP 认证插件会和 http_addition 插件产生冲突,具体表现是如果同一个 location 里开启了 auth_ldap 和 add_after_body,在输入用户名密码认证通过后,nginx 似乎不会发送网页数据,体现为浏览器一直转圈直到超时。暂时的解决办法只能是禁用 add_after_body:

# LDAP auth doesn't work well with http_addition, disable it
add_after_body "";

如上操作之后,指定的地址就启用了 LDAP 认证。如果将 nginx 作为反向代理代理其它内部服务,并屏蔽外网直接访问这些内部服务,就可以用 LDAP 统一保护认证这些服务。

为 nginx 启用 TLS 1.3,并传递给 FastCGI 后端

OpenSSL 在最新的测试版中提供了 TLS 1.3 的实验性支持,包括了一系列的性能和安全性优化。最新的 nginx 1.13 系列也相应的添加了 TLS 1.3 的相关选项。

不过由于 TLS 1.3 还处在草案状态,现在使用还是要踩一些坑的:

  1. TLS 1.3 目前有 18、19、20 三个版本的草案已经被 OpenSSL 等实现,而且它们互不兼容,也没有一个 SSL 的库把它们三合一。
  2. 目前 Chrome、Firefox 等浏览器广泛使用的是 18 版草案,但是这个版本的 OpenSSL 不支持 TLS 扩展,而 Certificate Transparency 需要用到它。
  3. 虽然最新的 nginx-ct 插件增加了 TLS 1.3 的 Certificate Transparency 支持,但是由于第二条,它无法与草案 18 的 OpenSSL 一同工作,会出现编译失败的情况。因此必须退回到 nginx-ct 项目 release 中的 1.3.2 版本,而这个版本对 TLS 1.3 不生效。

我部署 nginx 使用的 Dockerfile 如下:

FROM debian:jessie-slim
MAINTAINER Lan Tian "lantian@lantian.pub"
ENV NGINX_VERSION=1.13.3 OPENSSL_VERSION=tls1.3-draft-18 NGINX_CT_VERSION=1.3.2
RUN cd /tmp \
    && apt-get update -q \
    && apt-get -y upgrade \
    && apt-get -y install build-essential git autoconf automake libtool wget tar zlib1g-dev libpcre3 libpcre3-dev unzip libjemalloc-dev \
    && wget http://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz \
      && tar xf nginx-${NGINX_VERSION}.tar.gz \
    && git clone https://github.com/bagder/libbrotli.git \
      && cd /tmp/libbrotli && ./autogen.sh && ./configure && make && make install && cd /tmp \
      && ln -s /usr/local/lib/libbrotlienc.so.1 /usr/lib/libbrotlienc.so.1 \
    && git clone https://github.com/google/ngx_brotli.git \
      && cd /tmp/ngx_brotli && git submodule update --init && cd /tmp/ \
    && wget https://github.com/grahamedgecombe/nginx-ct/archive/v${NGINX_CT_VERSION}.zip \
      && unzip v${NGINX_CT_VERSION}.zip \
    && wget https://github.com/openssl/openssl/archive/${OPENSSL_VERSION}.zip \
      && unzip ${OPENSSL_VERSION}.zip \
    && cd nginx-${NGINX_VERSION} \
    && wget https://github.com/cloudflare/sslconfig/raw/master/patches/nginx__1.13.0_http2_spdy.patch \
      && patch -p1 < nginx__1.13.0_http2_spdy.patch \
    && wget https://github.com/cloudflare/sslconfig/raw/master/patches/nginx__1.11.5_dynamic_tls_records.patch \
      && patch -p1 < nginx__1.11.5_dynamic_tls_records.patch \
    && ./configure \
       --with-threads \
       --with-file-aio \
       --with-ipv6 \
       --with-http_ssl_module \
       --with-http_spdy_module \
       --with-http_v2_module \
       --with-http_gzip_static_module \
       --with-http_gunzip_module \
       --with-http_stub_status_module \
       --with-http_sub_module \
       --add-module=/tmp/nginx-ct-${NGINX_CT_VERSION} \
       --add-module=/tmp/ngx_brotli \
       --with-openssl=/tmp/openssl-${OPENSSL_VERSION} \
       --with-openssl-opt='enable-ec_nistp_64_gcc_128 enable-weak-ssl-ciphers enable-tls1_3 -ljemalloc' \
       --with-ld-opt="-ljemalloc" \
       --with-cc-opt="-O3 -flto -fPIC -fPIE -fstack-protector-strong -Wformat -Werror=format-security -Wno-deprecated-declarations" \
    && make \
    && make install \
    && cd / && rm -rf /tmp/* \
    && apt-get purge -y unzip git autoconf libtool automake build-essential \
    && apt-get autoremove -y --purge \
    && apt-get clean \
    && ln -sf /usr/local/nginx/sbin/nginx /usr/local/nginx \
    && ln -sf /dev/stdout /usr/local/nginx/logs/access.log \
      && ln -sf /dev/stderr /usr/local/nginx/logs/error.log
#EXPOSE 80 443
ENTRYPOINT /usr/local/nginx

该 Dockerfile 编译的 nginx 包含了 Brotli 压缩算法,Certificate Transparency(仅 TLS 1.2 及以下),OpenSSL 1.1.1 draft 18,以及 CloudFlare 的 SPDY 支持补丁和 Dynamic TLS Record 补丁。

如果你不使用 Docker,但是使用 Debian 或者 Ubuntu,可以直接用 RUN 后面的一长串命令来直接安装 nginx。

对配置文件的修改

nginx 的配置文件也需要修改,以开启 TLS 1.3 功能。在 nginx.conf 的 server 块里:

  1. 修改 ssl_protocols,添加 TLSv1.3
  2. 修改 ssl_ciphers,在最开头添加 TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-GCM-SHA256:TLS13-AES-128-CCM-8-SHA256:TLS13-AES-128-CCM-SHA256

修改完成的示例如下:

ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers 'TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-GCM-SHA256:TLS13-AES-128-CCM-8-SHA256:TLS13-AES-128-CCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';

FastCGI 检测 TLS 版本

由于 SSL 连接往往并不直接建立到程序语言服务端上,例如 PHP 等 FastCGI 服务端,这些服务端往往无法得知用户连接的 TLS 版本号和加密方式。但是,nginx 可以得知这些信息,并且把它们作为变量存储起来。简单的修改配置文件,就能把这些信息传递到 FastCGI 后端。

打开你 nginx 配置目录下的 fastcgi_params 文件,并向里面加入两行:

fastcgi_param  SSL_CIPHER         $ssl_cipher;
fastcgi_param  SSL_PROTOCOL       $ssl_protocol;

重新加载 nginx 配置即可。以 PHP 为例,这些信息可以通过类似 $_SERVER['SSL_CIPHER'] 的方式来获取。

配置完成的 Qualys SSL Labs 检查结果

插图

插图

nginx 配置并启用SSL和SPDY访问

来自 CloudFlare 博客的最新消息(http://blog.cloudflare.com/google-now-factoring-https-support-into-ranking-cloudflare-on-track-to-make-it-free-and-easy ),Google可能会在算权重的时候把网站支持SSL作为加分项目。因此我就给我的博客启用了SSL。

1.申请证书

SSL,在服务器端必须有证书。这个证书最好不要自己生成,否则大多数浏览器都会提示证书不受信任。

StartSSL是目前唯一一家颁发免费SSL证书并且受到大多数浏览器信任的证书颁发机构,可以根据 http://www.freehao123.com/startssl-ssl/ 这篇文章进行操作。

不过在实际操作中,由于StartSSL连接速度较慢,如果按照向导生成证书,中途可能卡住,而一旦卡住就要全部重来,我们可以自己生成证书请求,然后提交。

  1. 在你的Linux服务器(本例为Debian 7)上输入以下命令产生一个私钥:

  2. openssl genrsa -out privkey.pem 4096


  3. 输入以下命令产生证书请求(CSR文件),其中信息可以乱填,StartSSL不关心其中信息,他只关心你的私钥是多少。

  4. openssl req -new -key privkey.pem -out cert.csr
  5. cat cert.csr


  6. 把第五步的输出复制。

  7. 按照上面这篇文章操作,执行到这一步时选择Skip,然后粘贴入你的证书请求,然后下一步。

插图

2.安装证书

完成上面的步骤后,你应该(至少)有两个文件:一个是你的私钥,一个是证书文件。

如果你手动生成证书请求csr文件,那么privkey.pem就是你的私钥;如果你通过网页向导生成,私钥已经在向导过程中给出。

  1. 把你的证书保存为ssl.crt,以下操作将按照本文件名继续。如果你通过网页向导操作,把你的私钥保存到ssl.key。

  2. 如果通过网页向导操作,输入以下命令对你的私钥解密,密码是你在网页向导上填写的那个。如果不解密,每次nginx启动时都会要求你输入密码。

  3. openssl rsa -in ssl.key -out privkey.pem


  4. Firefox在证书验证上比较奇葩,它要求证书文件中保存有这个证书的颁发机构的证书。因此我们要进行如下操作:

  5. wget http://www.startssl.com/certs/ca.pem
    wget http://www.startssl.com/certs/sub.class1.server.ca.pem
    cat ssl.crt sub.class1.server.ca.pem ca.pem > ssl-unified.crt


  6. 此时你获得一个ssl-unified.crt文件,把它和privkey.pem移动到你喜欢的地方。

  7. (警告:不要复制到能够通过网页访问来下载的地方,比如把他们放在/var/www里就是作死行为!本例以放在/root下为例。)

  8. 编辑你的nginx配置文件。

  9. cd /etc/nginx/sites-enabled
  10. nano default #如果你的网站配置文件名不同,请替换。


  11. 在配置文件里你的网站那一段的listen 80下面输入:

  12. listen 443 ssl;
    ssl_certificate /root/ssl-unified.crt;
    ssl_certificate_key /root/privkey.pem;


  13. 重启nginx,安装结束。。

  14. service nginx restart


3.SPDY的安装和启用

SPDY是Google主导开发的一个网络协议,使用它,可以在一个SSL连接内同时传输好几路数据。

比如,打开网页时,如果没有SPDY,那么浏览器就要同时打开好几路SSL连接下载数据。问题是,这些连接不是同时打开的,一般情况下网页加载到一半,浏览器发现“哎呀我需要这个东西但是还没下载”,才会开新连接下载数据。

而有了SPDY,浏览器可以直接在同一个SSL连接中下载数据,省去了连接和SSL验证的时间。

  1. 更新nginx版本。Debian软件源里默认的nginx不带有SPDY功能,需要把nginx替换成nginx-full。

  2. apt-get install nginx-full


  3. 编辑你的nginx配置文件,把

  4. listen 443 ssl;


  5. 改成

  6. listen 443 ssl spdy;


  7. 完事。

启用后,如果你的网站的图片、CSS、JS等都从你的服务器上下载,将会较大地改善你的网站在SSL下的载入速度。