NginxでNTLM認証をProxyする

### はじめに

Nginxをリバース・プロキシとして使用するとき、アップストリーム・サーバでWindows認証（NTLM認証）をとっていると、上手く動かない問題があります。

こういうときには、[Re: NTLM sharepoint when use nginx reverse proxy](https://forum.nginx.org/read.php?2,278737,278742) にもあるように、NTLM directiveを利用するのですが、これがなんと商用パッケージという残念な仕様なのです（[ngx_http_upsteam_module](http://nginx.org/en/docs/http/ngx_http_upstream_module.html#ntlm)）。

万策尽きたか、と思いましたが、どうやらNginxをソースコードからビルドすることができる人なら、
NTLM directiveを追加できるようです。

ソースコードは以下の場所です。

[GitHub - gabihodoroaga/nginx-ntlm-module: A nginx module to allow proxying requests with NTLM Authentication.](https://github.com/gabihodoroaga/nginx-ntlm-module)

では、Nginxとこのnginx-ntlm-moduleなるものを合体させるには、どうしたら良いかについては、Readmeの通りなのですが、私自身、まずNginxのmakeをやったことがなかったので、まずそこからやり方を調べてになりました。

[Building nginx from Sources](https://github.com/gabihodoroaga/nginx-ntlm-module)

### 想定環境

- proxy環境下にあるUbuntu 20.04 LTS
- upstreamのwindows認証付きサービスは構築済み

### STEP1.　すでにAPTでインストール済みのNginxをアンインストール

```bash
$ sudo su -
## (aptで設定ファイルを含めてアンインストールする)
# apt purge nginx*
# apt autoremove
# 
```

### STEP2.　Nginxとnginx-ntlm-moduleのソースを取得

資産が置いてあるサイトでバージョンとURLを確認します。
[Index of /download/ (nginx.org)](http://nginx.org/download/)

```bash
## (proxyの設定を行う)
# export http_proxy=http://{username}:{password}@{proxy server}:{port}
# export https_proxy=http://{username}:{password}@{proxy server}:{port}
# 
# cd /usr/local/src
# curl http://nginx.org/download/nginx-1.19.8.tar.gz --output ./nginx-1.19.8.tar.gz
# tar zxf nginx-1.19.8.tar.gz
# 
# git clone https://github.com/gabihodoroaga/nginx-ntlm-module.git
#
```

### STEP3. Makeの下準備

makeを実行するのに必要なパッケージをインストールします。

```bash
## インストールパッケージについて
## gcc : nginxをビルドするのに必要
## make : makeコマンドを実行するのに必要
## ------ 以降はNginxで使うモジュールによって、要不要が分かれますが、今回は全部盛りで行くので全てインストールします ------
## libpcre3, libpcre3-dev : HTTP rewriteモジュールを有効にするのに必要
## libssl-dev : OpenSSL モジュールを有効にするのに必要
## zlib1g-dev : HTTP gzipモジュールを有効にするのに必要
## libxml2-dev libxlst1-dev : HTTP XSLTモジュールを有効にするのに必要
## libgd-dev : HTTP image filterモジュールを有効にするのに必要
## libgeoip-dev : Geo IPモジュールを有効にするのに必要
## libperl-dev : Perlモジュールを有効にするのに必要
# apt install gcc make libpcre3 libpcre3-dev libssl-dev zlibg-dev libxml2-dev libxlst1-dev libgd-dev libgeoip-dev libperl-dev
```

nginxにnginx-ntlm-moduleを組み入れるためには、configureする必要があります。

```bash
# cd /usr/local/src/nginx-1.19.8
## 今回は全部盛りなので、少しでも軽量にするためにdynamicにできるmoduleはdynamicにしています。
## 以下はバックスラッシュを使って、1行として扱ってください（見やすくするため省略しています）。
# ./configure 
#     --prefix=/usr/local/nginx
#     --with-select_module
#     --with-poll_module
#     --with-threads 
#     --with-file-aio
#     --with-http_ssl_module 
#     --with-http_v2_module
#     --with-http_realip_module 
#     --with-http_addition_module
#     --with-http_xslt_module=dynamic
#     --with-http_image_filter_module=dynamic
#     --with-http_geoip_module=dynamic
#     --with-http_sub_module
#     --with-http_dav_module
#     --with-http_flv_module
#     --with-http_mp4_module
#     --with-http_gunzip_module
#     --with-http_gzip_static_module
#     --with-http_auth_request_module
#     --with-http_random_index_module
#     --with-http_secure_link_module
#     --with-http_degradation_module
#     --with-http_slice_module
#     --with-http_stub_statuc_module
#     --with-http_perl_module
#     --with-stream=dynamic
#     --with-stream_ssl_module
#     --with-stream_realip_module
#     --with-stream_geoip_module=dynamic
#     --with-stream_ssl_preread_module
#     --add-dynamic-module=../nginx_ntlm_module
#     --with-debug
```

### STEP4. Makeの実行

```bash
# make
# make install
# cd /usr/local/nginx/sbin
## APTパッケージ版のNginxが稼働していないかチェック
## master process や worker processが稼働していなければOK
# ps aux | grep nginx
# ./nginx -v
## バージョンが帰ってくれば、実行ファイルの生成は成功
## Nginxを試験稼働させる
# ./nginx -t -q -g 'daemon on; master_process on;' # pre test
# ./nginx -g 'daemon on; master_process on;'
```

Web ブラウザでアクセスすると、Nginxのwelcomeページが表示されるはずです。
もし、正常に表示されない場合は、firewallの設定を確認してみてください。

```bash
# ufw status 
## 80番ポートを許可するRuleがあることを確認
## Ruleがなければ、以下を実行
# ufw allow 80
# ufw reload
# ufw status
```

### STEP5. NginxのUnitファイルを作成

aptを使ってインストールすると、下記の内容のUnitファイルが、`/lib/systemd/system/nginx.service`⁠ に自動的に生成され、enableされます。
しかし、今回はソースからビルドしたので、それらを自分で行う必要があります。
まずは、お作法に則って、Unitファイルを作成します。作成する場所は、`/etc/systemd/system/nginx.service` です。

```bash
# Stop dance for nginx
# =======================
# ExecStop sends SIGSTOP (graceful stop) to the nginx process.
# If, after 5s (--retry QUIT/5) nginx is still running, systemd takes control
# and sends SIGTERM (fast shutdown) to the main process.
# After another 5s (TimeoutStopSec=5s), and if nginx is alive, systemd sends
# SIGKILL to all the remaining process in the process group (KillMode=mixed).
#
# nginx signals reference doc:
# http://nginx.org/en/docs/control.html
#
[Unit]
Description=A high performance web server and a reverse proxy server
Documentation=man:nginx(8)
After=network.target

[Service]
Type=forking
PIDFile=/usr/local/nginx/logs/nginx.pid
ExecStartPre=/usr/local/nginx/sbin/nginx -t -q -g 'daemon on; master_process on;'
ExecStart=/usr/local/nginx/sbin/nginx -g 'daemon on; master_process on;'
ExecReload=/usr/local/nginx/sbin/nginx -g 'daemon on; master_process on;' -s reload
ExecStop=/sbin/start-stop-daemon --quiet --stop --retry QUIT/5 --pidfile /usr/local/nginx/logs/nginx.pid
TimeoutStopSec=5
KillMode=mixed

[Install]
WantedBy=multi-user.target
```

試験稼働させていたNginxのプロセスをKILLしておきます。

```bash
# kill {nginxのprocess id}
```

systemdに作成したUnitファイルを読み込ませます。

```bash
# systemctl daemon-reload
# systemctl enable nginx.service
# systemctl start nginx.service
# systemctl status nginx.service
## この結果がActive: active(running)になっていればOK
```

### STEP6. Nginxの設定ファイルをAPTパッケージ版と合わせる

aptパッケージ版で生成される⁠`/etc/nginx/nginx.conf` と今回makeで生成される⁠`/usr/local/nginx/conf/nginx.conf` とを見比べると、かなり差異があります。そのため、使い慣れたaptパッケージ版に設定を可能な限り近づけたいと思います。

```nginx
user www-data;
worker_processes auto;
pid logs/nginx.pid;

events {
 worker_connections 768;
}

http {
 sendfile on;
 tcp_nopush on;
 tcp_nodelay on;
 keepalive_timeout 65;
 types_hash_max_size 2048;
 
 include mime.types;
 default_type application/octet-stream;

 ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
 ssl_prefer_server_ciphers on;

 access_log /var/log/nginx/access.log;
 error_log /var/log/nginx/error.log;

 gzip on;

 include /usr/local/nginx/conf/conf.d/*.conf;
 include /usr/local/nginx/sites-enabled/*;
} 
```

logフォルダを作成しておきます。

```bash
# mkdir /var/log/nginx
```

default設定を読み込ませるためにフォルダを作成して、defaultを作成しておきます。

```bash
# mkdir /usr/local/nginx/sites-enabled
# chmod 755 /usr/local/nginx/sites-enabled
# cd /usr/local/nginx/sites-enabled
# touch default
# chmod 777 default
# vi default
## defaultの設定内容は以下の通りです。
```
```nginx
server {
 listen 80 default_server;
 listen [::]:80 default_server;

 root /var/www/html;

  index index.html index.htm index.nginx-debian.html;

 server_name _;

 location / {
  try_files $uri $uri/ =404;
 }
}
```

設定を反映させるためにNginxを再起動します。

```bash
# systemctl restart nginx.service
```

再起動後、http://{hostname}/にアクセスすると404 Not Found のページが表示されるはずです。
ここまでやって、いよいよnginx-ntlm-moduleの出番です。

余談ですが、こうした作業をやると、日頃自分たちがいかに甘やかされているかが良くわかります。
aptコマンド一発で構築できてしまえる素晴らしさを体感できるいい例ですね。

### STEP7. NTLM directiveの有効化

先ほど変更した`/usr/local/nginx/conf/nginx.conf`に、load_moduleの行を追加します。

```nginx
user www-data;
worker_processes auto;
pid logs/nginx.pid;

# dynamic moduleにしているので、ここでNTLMモジュールを読み込む
load_module /usr/local/nginx/modules/ngx_http_upstream_ntlm_module.so; 

events {
 worker_connections 768;
}
```

STEP8. confファイルの作成

`/usr/local/nginx/conf/conf.d/{config名}.conf` を作成し、以下のように編集します。

```nginx
upstream ntlm_service_name {
 server 0.0.0.0:80; # NTLM認証を持つアップストリーム・サーバ
 ntml;
}

server {
 listen 80;
 server_name proxy_server_name;
 location / {
  proxy_pass     http://ntlm_service_name/;
  proxy_http_version 1.1;
  proxy_set_header  Connection "";
}
```

再度、設定を反映させるためにNginxを再起動します。

```bash
# systemctl restart nginx.service
```

これで、Nginxをリバース・プロキシとして使用するとき、アップストリーム・サーバでWindows認証（NTLM認証）をとっていても、
404や401でエラーになることはなくなったと思いますが、いかがでしょうか？

読了、お疲れさまでした。