OpenResty（Nginx）+Lua+OpenLDAP+ngx_auth_modでLdap認証と認可を実装する

# はじめに

以下の記事でOpenResryとLuaを用いて、Basic認証と認可を実装しました。

- [OpenResty（Nginx）とluaでBasic認証＋認可を実装する](https://qiita.com/v1tam1n/items/4e299d4411d407e2866c)

今回の記事では、応用してLdap認証をできるようにしてみます。

# ngx auth mod

NginxでLdap認証をするためのツールとして、

- [iij/ngx_auth_mod](https://github.com/iij/ngx_auth_mod)

を利用します。

IIJがGithubに公開しているものです。公開の経緯などは以下に分かりやすくまとめられています。

- [面倒くさいから nginx の LDAP 認証モジュール公開したよ](https://eng-blog.iij.ad.jp/archives/13747)


# OpenLDAP

>OpenLDAP ソフトウェアは、 Lightweight Directory Access Protocolのオープン ソース 実装です 。
引用：[OpenLDAP](https://www.openldap.org/)

ディレクトリサービスへアクセスるためのLdapプロトコルの機能を実装しています。

Dockerコンテナで簡単に構築ができるため採用しました。イメージは以下を利用します。

- [osixia/openldap](https://hub.docker.com/r/osixia/openldap)


# ソース

構築方法などはGithubのREADMEに書いています。必要なソースは以下からクローンして利用してください。

- [v1tam1nb2/openresty-ldap-auth](https://github.com/v1tam1nb2/openresty-ldap-auth)


# セットアップ

```bash
# ソースクローン
git clone https://github.com/v1tam1nb2/openresty-ldap-auth.git
# ディレクトリ移動
cd openresty-ldap-auth
# ビルド
docker-compose build
# コンテナ起動
docker-compose up -d
# テストユーザー投入（test001～test005）
docker exec ldap-server ldapadd -x -D "cn=admin,dc=my-company,dc=com" -w admin -f /openldap/testuser.ldif -ZZ
```

# アクセス確認

- http://localhost:8080
    - phpLDAPadminにアクセスできます。ブラウザからLdapのユーザー追加などができるサービスです

以下の情報でログインできます。
```
Login DN: cn=admin,dc=my-company,dc=com
Password: admin
```

- http://localhost
    - OpenRestyのデフォルトページにアクセスします。認証を問われます。

test001ユーザーのパスワードはtest001というように、test001～test005の5つのテストユーザーを投入しています。

Luaの認可スクリプトでtest001ユーザーのみアクセスを許可しています。

# ngx auth modをコンテナで利用する方法

Ubuntu 20.04のベースイメージにGo言語をインストールして、ngx_auth_modをビルドしました。
Go言語のベースイメージを使っても良かったかもしれません。

```Dockerfile:Dockerfile
FROM ubuntu:20.04

ARG GO_VERSION="1.20.2"

ENV DEBIAN_FRONTEND="noninteractive"

# 最低限のコマンドインストール
# golangのセットアップ
RUN apt-get update && apt-get upgrade -y && \
    apt-get install -y wget curl vim openssl git zip jq gettext-base && \
    # クリーニング
    rm -rf /root/.cache && rm -rf /tmp/*\
    apt-get autoremove && apt-get autoclean && apt-get clean && rm -fr /var/lib/apt/lists/*

# golang install
RUN wget https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz && \
    tar -xzf go${GO_VERSION}.linux-amd64.tar.gz -C /usr/local/ && \
    echo 'export PATH=$PATH:/usr/local/go/bin' >> /root/.profile && \
    rm go${GO_VERSION}.linux-amd64.tar.gz && \
    rm -rf /root/.cache && rm -rf /tmp/*

# nginx auth mod build
RUN cd /usr/local && \
    git clone https://github.com/iij/ngx_auth_mod.git -b master --depth 1 && \
    . /root/.profile && \
    cd ngx_auth_mod && ./build.sh && \
    mv bin/* /usr/local/bin/

COPY ./src/ngx_auth_mod/auth-ldap.conf /ngx_auth_mod/auth-ldap.conf

ENV DEBIAN_FRONTEND=""

# コンテナ起動時の実行コマンド設定
ENTRYPOINT [ "/usr/local/bin/ngx_ldap_auth","/ngx_auth_mod/auth-ldap.conf"]
```

設定は以下を参考にしました。

- [ngx_ldap_auth](https://github.com/iij/ngx_auth_mod/blob/master/docs_ja/ngx_ldap_auth.md)

コンテナで利用する際の注意点として、`socket_path`のIPを`127.0.0.1`ではなく、`0.0.0.0`にする必要がありました。今回はコンテナとして利用するので、コンテナ内で`127.0.0.1`を指定してもコンテナの外からアクセスできません。

```conf:auth-ldap.conf
socket_type = "tcp"
socket_path = "0.0.0.0:9200"
#cache_seconds = 0
#use_etag = true
auth_realm = "TEST Authentication"

host_url = "ldaps://ldap-server"
start_tls = 0
skip_cert_verify = 1
# root_ca_files = [
# 	"/etc/ssl/certs/Local-CA-Chain.cer",
# ]

base_dn = "DC=my-company,DC=com"
bind_dn = "CN=%s,DC=my-company,DC=com"
#uniq_filter = "(memberOf=CN=%s,DC=my-company,DC=com)"
timeout = 5000
```


# OpenLDAP

`my-company.com`というドメインにしています。


```yaml
  ldap-server:
    image: osixia/openldap:latest
    restart: always
    container_name: ldap-server
    environment:
      LDAP_ORGANISATION: "My Company"
      LDAP_DOMAIN: "my-company.com"
      LDAP_ADMIN_PASSWORD: "admin"
      LDAP_READONLY_USER: "true"
      LDAP_READONLY_USER_USERNAME: "readonly"
      LDAP_READONLY_USER_PASSWORD: "readonly_password"
      LDAP_TLS_VERIFY_CLIENT: "never" 
    ports:
      - "389:389"
    volumes:
      - ./src/openldap/testuser.ldif:/openldap/testuser.ldif
  
  ldap-admin:
    image: osixia/phpldapadmin:latest
    container_name: ldap-admin
    restart: always
    environment:
      PHPLDAPADMIN_LDAP_HOSTS: "ldap"
      PHPLDAPADMIN_HTTPS: "false"
    ports:
      - "8080:80"
    links:
      - "ldap-server:ldap"
```

# OpenRestyの設定

`defalut.conf`以外はデフォルトのままです。

```conf:default.conf
# nginx.vh.default.conf  --  docker-openresty
#
# This file is installed to:
#   `/etc/nginx/conf.d/default.conf`
#
# It tracks the `server` section of the upstream OpenResty's `nginx.conf`.
#
# This config (and any other configs in `etc/nginx/conf.d/`) is loaded by
# default by the `include` directive in `/usr/local/openresty/nginx/conf/nginx.conf`.
#
# See https://github.com/openresty/docker-openresty/blob/master/README.md#nginx-config-files
#

# ngx_ldap_auth service
upstream auth_req {
    server ngx_ldap_auth:9200; 
}

server {
    listen       80;
    server_name  localhost;

    #charset koi8-r;
    #access_log  /var/log/nginx/host.access.log  main;

	# Authentication URL path
	auth_request /auth;
	proxy_set_header Authorization "";
	location = /auth {
		proxy_set_header X-Forwarded-User $remote_user; # Required if using modules other than ngx_ldap_auth.
		proxy_set_header Context-Length "";
		proxy_pass_request_body off;
		proxy_pass http://auth_req;
	}

    location / {
        root   /usr/local/openresty/nginx/html;
        index  index.html index.htm;
        # auth_basic "basic auth";
        # auth_basic_user_file /etc/nginx/.htpasswd;
        access_by_lua_file /etc/nginx/lua/auth.lua;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/local/openresty/nginx/html;
    }

    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;
    #}

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #    root           /usr/local/openresty/nginx/html;
    #    fastcgi_pass   127.0.0.1:9000;
    #    fastcgi_index  index.php;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    #    include        fastcgi_params;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}
```
# 認可スクリプト（Lua）

```lua:auth.lua
-- remote user名を取得
local remote_user = ngx.var.remote_user
if remote_user ~= "test001" then
    -- test001ユーザーでなければ401
    ngx.header.content_type = "text/plain"
    ngx.log(ngx.STDERR, remote_user.." にはアクセス権がありません")
    ngx.status = ngx.HTTP_UNAUTHORIZED
    return ngx.exit(ngx.status)
end
```


# まとめ

簡単な記事になってしまいましたが、無事ngx_auth_modを利用してLdap認証と認可が実装できました。
OpenLDAPを利用することで、ローカル環境でも気軽にLdap認証を試すことができました。