正文
前言
很多时候,我们需要给一些本没有身份认证功能的业务增加一个认证模块。
-
比如免费版的 ELK,Kibana 上是没有身份认证的;
-
比如 0.1 版的 Open-Falcon,Dashboard 上也是没有认证的;
-
又或者一些本来对外公开的网站,突然在某些特殊的日子,在某些特殊的时间里,不希望对外公开了。。。
直接修改业务的侵入式方案通常不太容易,非侵入式的方案一般也能实现类似的效果,比如给他增加一个代理然后做 http basic 认证。
这是一个好办法,但是 http basic 认证毕竟太简单了,也不方便集成外部的认证源,比如 LDAP
所以一个更灵活的方案是通过 Nginx 的 auth_request 模块
Nginx 的 auth_request 模块
auth_request 大抵就是在你访问 Nginx 中受 auth_reuqest 保护的路径时,去请求一个特定的服务。根据这个服务返回的状态码,auth_request 模块再进行下一步的动作,允许访问或者重定向跳走什么的。因此我们可以在上面去定制我们所有个性化的需求。
假定我们的环境是 centos ,yum 安装 nginx 就略了。由于通过 yum 等安装的 nginx 默认没有编译 auth_request 模块。我们需要重新编译一下。
先运行
nginx -V
来获取当前
nginx
的编译参数
# nginx -V
nginx version: nginx/1.14.0
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-16) (GCC)
built with OpenSSL 1.0.2k-fips 26 Jan 2017
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib64/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -fPIC' --with-ld-opt='-Wl,-z,relro -Wl,-z,now -pie'
先安装一些依赖
yum -y install gcc gcc-c++ autoconf automake make
yum -y install zlib zlib-devel openssl
yum -y install openssl-devel pcre pcre-devel
yum -y install libxslt-devel
yum -y install redhat-rpm-config
yum -y install gd-devel
yum -y install perl-devel perl-ExtUtils-Embed
yum -y install geoip-devel
yum -y install gperftools-devel
然后下载
nginx
的
源代码
,用刚才得到的编译参数,增加
--with-http_auth_request_module
参数重新编译
# ./configure --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib64/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -fPIC' --with-ld-opt='-Wl,-z,relro -Wl,-z,now -pie' --with-http_auth_request_module
# make
# make install
再
nginx -V
看一下,已经带上
http_auth_request_module
了
# nginx -V
nginx version: nginx/1.14.0
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-28) (GCC)
built with OpenSSL 1.0.2k-fips 26 Jan 2017
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib64/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -fPIC' --with-ld-opt='-Wl,-z,relro -Wl,-z,now -pie' --with-http_auth_request_module
一个简单 demo
nginx.inc
给了一个非常简单的 demo,即
nginx-auth-ldap
这个 repo。
整个逻辑就如下图所示
image.png
详细的流程图大抵如下所示:
image.png
-
客户端发送 HTTP 请求,以获取 Nginx 上反向代理的受保护资源。
-
Nginx 的
auth_request
模块 将请求转发给
ldap-auth
这个服务(对应 nginx-ldap-auth-daemon.py),首次肯定会给个 401 .
-
Nginx 将请求转发给
http:// backend / login
,后者对应于这里的后端服务。它将原始请求的
uri
写入
X-Target
,以便于后面跳转。
-
后端服务向客户端发送登录表单(表单在
demo
代码中定义)。根据
error_page
的配置,Nginx 将登录表单的 http 状态码返回 200。
-
用户填写表单上的用户名和密码字段并单击登录按钮,从向
/ login
发起
POST
请求,Nginx 将其转发到后端的服务上。
-
后端服务把用户名密码以 base64 方式写入 cookie。
-
客户端重新发送其原始请求(来自步骤1),现在有 cookie 了 。Nginx 将请求转发给
ldap-auth
服务(如步骤2所示)。
-
ldap-auth
服务解码 cookie,然后做 LDAP 认证。
-
下一个操作取决于 LDAP 认证是否成功:
Demo 测试
先安装下依赖
yum install python-ldap
然后把 repo clone 下来
#git clone https://github.com/nginxinc/nginx-ldap-auth.git
# ls
backend-sample-app.py Dockerfile nginx-ldap-auth.conf nginx-ldap-auth-daemon-ctl.sh nginx-ldap-auth.default nginx-ldap-auth.service rpm
debian LICENSE nginx-ldap-auth-daemon-ctl-rh.sh nginx-ldap-auth-daemon.py nginx-ldap-auth.logrotate README.md
这其中
nginx-ldap-auth.conf
是 Nginx 的配置范例,直接 copy 过去即可
# cp nginx-ldap-auth.conf /etc/nginx/nginx.conf
Nginx 的配置文件如下,做了些精简,加了中文注释。
error_log logs/error.log debug;
# 这里把日志放在 nginx 目录下,所以要么改掉要么在 nginx 目录下建个 log 目录
events { }
http {
# cache 路径和大小
proxy_cache_path cache/ keys_zone=auth_cache:10m;
# 将要被 nginx auth_request 保护的 backend
# 在这个 demo 里是 backend-sample-app.py.
upstream backend {
server 127.0.0.1:9000;
}
# nginx 服务起在 8081 上
server {
listen 8081;
# 这个路径被 auth_request 保护了, 401 重定向到 login 上
location / {
auth_request /auth-proxy;
# redirect 401 to login form
error_page 401 =200 /login;
proxy_pass http://backend/;
}
# 这里是我们认证的页面
location /login {
proxy_pass http://backend/login;
# 这个 X-Target 是给认证完以后重定向的
proxy_set_header X-Target $request_uri;
}
# 这是用做 auth_request 请求的路径
location = /auth-proxy {
internal;
# 提供 ldap 认证服务的 auth-proxy backend
# 这个 demo 里是 nginx-ldap-auth-daemon.py.
proxy_pass http://127.0.0.1:8888;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_cache auth_cache;
proxy_cache_valid 200 10m;
# cookie 会加在这里
proxy_cache_key "$http_authorization$cookie_nginxauth";
# ldap 的地址
proxy_set_header X-Ldap-URL "ldap://ldap.example.org";
# 是否开启 starttls
# 注意 starttls 不能和 tls,也就是 ldaps 同时开启
#proxy_set_header X-Ldap-Starttls "true";
# ldap 的 BaseDN
proxy_set_header X-Ldap-BaseDN "dc=example,dc=org";
# ldap 的 binddn,也就是有查询权限的账号
proxy_set_header X-Ldap-BindDN "cn=manager,dc=example,dc=org";
# binddn 的密码
proxy_set_header X-Ldap-BindPass "password";
# cookie 的名字和值
proxy_set_header X-CookieName "nginxauth";
proxy_set_header Cookie nginxauth=$cookie_nginxauth;
# ldap 的 searchFilter,就是拿哪个字段作为认证的用户名
proxy_set_header X-Ldap-Template "(uid=%(username)s)";
}
}
}
然后分别执行
./nginx-ldap-auth-daemon.py
和
./backend-sample-app.py
即可。