OpenVPNのユーザー認証のLDAP化

# はじめに
OpenVPNサーバの証明書を延長したので、今度はユーザー認証周りを調べてみました。

現状、OpenVPN接続時のユーザー管理台帳が個別のsqlite DBに存在する環境でのお話です。

いわゆるユーザーアカウント、パスワード情報があちこちにあることは管理上よろしくないですし、ユーザー認証をLDAP環境に直接問い合わせることで、状況管理稼働を減らせないかなと思って調べた内容をまとめたものです。

一度試験はしましたが、LDAP環境の整備が終わってないので実運用はまだしていませんが、正常に認証、接続が行えるところまでは一応確認済みです。

# OpenVPNのユーザー認証のLDAP化

OpenVPNで、証明書とは別にユーザー名/パスワード認証を行う手順はオフィシャルにも記載されています。

https://www.openvpn.jp/document/openvpn-sqlite-auth/

上記からたどれるリンクにダウンロードファイルはないですが、サイト内にコードは記述してあるので、ちゃんと読めば上記サイト内の方法でsqliteのDB内にユーザー情報を格納して、証明書とは別にユーザー認証を実施する方法が確認できるかと思います。

しかし、この手順の場合、サーバ上に個別にユーザーのアカウントDBを用意することになるので、いわゆる認証基盤の考え方からするとあまりよろしくないようにも思えます。

いろいろな解決方法があると思いますが、今回はこのユーザー認証部分をLDAPに直接問い合わせをかける方法に変更する手順をなるべく簡素に書いてみます。

なお今回の記述はWindows Serverで構築されたOpenVPNサーバについての内容ですが、パス情報等読み替えたりすればLinuxサーバでも流用可能なのではないかと思います。

# 前提条件

すでにPythonが導入された環境下の前提です。Python3導入環境での内容です。Python2でも問題なく動くかは検証していません。

# ライブラリのインストール
Pythonのldap3モジュールをインストールします。
~~~
# pip install ldap3
~~~
# Pythonスクリプトの作成
「ldap_auth.py」の名前で以下を作成します。

~~~ldap_auth.py
import sys
from ldap3 import Server, Connection, ALL

ldap_server = 'ldap.example.co.jp'
#LDAPサーバのアドレス 各LDAPの環境に合うように記述　なお通常はスレーブサーバを指定する
ldap_user_dn = 'cn=Manager,dc=example,dc=co,dc=jp' #管理者DN 各LDAPの環境に合うように記述
ldap_password = 'PASSWORD' 
#管理者パスワード 各LDAPの環境に合うように記述
user_dn_template = 'uid={},ou=People,dc=example,dc=co,dc=jp' 
#ユーザDNテンプレート 各LDAPの環境に合うように記述
def ldap_auth(username, password):
    try:
        server = Server(ldap_server, get_info=ALL)
        conn = Connection(server, ldap_user_dn, ldap_password, auto_bind=True)   
        user_dn = user_dn_template.format(username) 
        #ユーザーDNをフォーマット
        if conn.bind():
        #LDAP管理者としてバインドした後、ユーザとして認証を試みる
            user_conn = Connection(server, user_dn, password, auto_bind=True) 
            if user_conn.bind():
                return True
            else:
                print("User auth failed.")
                return False
    except Exception as e:
        print("LDAP error:", e)
        return False
    finally:
        if conn and not conn.closed:
            conn.unbind()

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("Usage: python ldap_auth.py <file>")
        sys.exit(1)

    filename = sys.argv[1]
    fp = open(filename)
    data = fp.readlines()
    fp.close()
    username = data[0].rstrip()
    password = data[1].rstrip()

    if ldap_auth(username, password):
        sys.exit(0)
    else:
        sys.exit(1)
~~~

# バッチファイルの作成
上記のpythonスクリプトを呼び出すバッチファイルを作成します。
これは前述したsqliteでの認証方法でも同じ形式のものを利用しています。
~~~ldap_auth.bat
@echo off
【Python実行ファイルへのフルパス】\python.exe 【ldap_auth.pyファイルへのフルパス】\ldap_auth.py %1
exit /b %ERRORLEVEL%
~~~
# OpenVPNの設定の変更

Windows 環境なので、設定ファイル「server.ovpn」に記述を追加します。ファイルパスは環境によって異なりますので参考までに。

~~~server.ovpn
auth-user-pass-verify "C:\\OpenVPN\\config\\scripts\\ldap_auth.bat" via-file
~~~

なお、前述のsqliteでの認証を設定していた場合は、
~~~server.ovpn
setenv auth_sqlite_db 'C:\\OpenVPN\\config\\scripts\\vpnusers.db'
~~~
のようなsqliteのDB指定をコメントアウト(削除でも可)して、
auth-user-pass-verify の記述内容を変更する形になります。

# おわりに

これで、OpenVPNへの接続時に、設定に則ってバッチファイル⇒pythonスクリプトが実行され、OpenVPN の機能でクライアントに要求したアカウントとパスワード情報を使い、LDAP認証を行ってくれるようになります。

認証基盤としてLDAPを構築している環境でないと意味がないんですが、Pythonスクリプト等で様々な認証基盤に直接問い合わせをかけることができるので、おそらく応用すればADに対しても問い合わせができるのではないかと思います。

また、やってる内容はそこまで複雑ではないので、Linux環境での適用も可能ではないかと思います。