daily dayflower

2007-07-11

Apacheで統合Windows認証を使う

前書き

統合 Windows 認証とは,ドメインの認証情報を使って HTTP サーバに認証してもらう方式です。Windows クライアントドメインログインしていれば,認証ダイアログが出現することなく自動的に認証されます。統合 Windows 認証には以下の2通りがあります。

今回はわけあって NTLM 認証を扱います。

Apache on Unix*1 で NTLM 認証をサポートするものには,有名なもので以下の物があります。

前者 2 つはほぼ同じもの(2 つめが改良版で Apache 2.2 にも対応している)ですが,後者の mod_auth_ntlm_winbind は次の利点があります。

  • Samba に付属の ntlm_auth ヘルパコマンド*2をバックエンドに利用 / おまかせしている
  • そのため所属グループ単位の認証等ができる
  • NTLMv2 に対応している(らしい);Vistaデフォルト設定でも OK
  • SPNEGO 認証に対応している(はず);オープン規格万歳 & よりシンプルなネゴシエーション
  • Basic 認証にも対応している*3

デメリットとしては,

  • Samba suite がインストールされている必要がある
  • winbindd がきちんと設定されている必要がある
  • smbd / nmbd も立ち上がっているほうがいいと思う
  • バックエンドとして ntlm_auth を子プロセスとして起動するのでちょっと重そう

一応,ntlm_auth コマンドは同一 connection だと使い回すのでそこまで重さについては気遣わなくてもいいはず。あと,どうやら smbd / nmbd だけではなく winbindd も立ち上がってなくてもよいみたいです。ただし smb.conf の winbind のための設定はしておく必要があります。その代わり NTLM だとどうかわかりませんが少なくとも SPNEGO だと,net join してある必要があります。

やっぱり winbindd が立ち上がってないとダメなケースに遭遇しました。あと,ntlm_auth というヘルパスクリプトは httpd のサーバ権限で起きるのですが,/var/cache/samba/winbindd_privileged/pipe にアクセスできるようにしておく必要があります。

今回は mod_auth_ntlm_winbind を扱います。単純に Unofficial mod_ntlm に気づかなかっただけです。

NTLM 認証の流れ

The NTLM Authentication Protocol and Security Support Provider】が参考になります。

  1. Client がコンテンツを要求する
  2. Server が 401 Unauthorized を返す
    • サポートしている認証様式として WWW-Authenticate: NTLM を返す
    • ここで一度 Connection を切る
  3. Client が Authorization: NTLM hogehoge というヘッダつきでコンテンツを要求する
    • hogehoge は nagotiate パケット
    • ここから keep-alive 接続である必要がある
  4. Server が 401 Unauthorized を返す
    • WWW-Authenticate: NTLM fugafuga ヘッダを返す
    • fugafuga は challenge パケット
  5. Client が Authrorization: NTLM gomogomo というヘッダつきでコンテンツを要求する
  6. Server は authenticate パケットを認証し,正しければ 200 OK を返す
  7. ここから同じコネクションで WWW-Authenticate / Authorizatation ヘッダなしでコンテンツをやりとりする

mod_auth_ntlm_winbind のビルド

どうやら Fedoraレポジトリに入るみたいです。ですがとりあえず今のところは手ビルド,ってことで。

配布元から必要なファイルをダウンロードします。configure.in, Makefile.in, mod_auth_ntlm_winbind.c が必須です。また .in しかないことからおわかりのとおり,autoconf も必要です。ほか RedHat 系なら apxs が必要なので httpd-devel もインストールしておいてください。

そのままビルドして一応動くのですが,いくらか気にくわないところがあったので,パッチをおいておきます。

--- mod_auth_ntlm_winbind.c.orig        2007-01-29 13:00:09.000000000 +0900
+++ mod_auth_ntlm_winbind.c 2007-07-11 12:22:13.000000000 +0900
@@ -80,4 +80,10 @@
 #endif
 
+#if defined(APACHE2) && AP_SERVER_MINORVERSION_NUMBER >= 2
+#define APACHE22 1
+#include "ap_provider.h"
+#include "mod_auth.h"
+#endif
+
 /* The name of the NTLM authentication scheme.  This appears in the
    'WWW-Authenticate' header in the initial HTTP request. */
@@ -490,5 +496,5 @@
 /* Call winbind to authenticate a (user, password)
    pair */
-static int winbind_authenticate_plaintext( request_rec *r, ntlm_config_rec * crec, char *user, char *pass)
+static int winbind_authenticate_plaintext( request_rec *r, ntlm_config_rec * crec, const char *user, const char *pass)
 {
     ntlm_connection_context_t *ctxt = get_connection_context( r->connection );
@@ -917,5 +923,5 @@
         } else
             sent_pw = "";
-        if ((s = strchr(sent_user, '\\')) != NULL
+        if (1 || (s = strchr(sent_user, '\\')) != NULL
             || (s = strchr(sent_user, '/')) != NULL) {
 
@@ -950,4 +956,10 @@
                                           : "Authorization");
     const char *auth_line2;
+#ifdef APACHE22
+    const char *current_auth = ap_auth_type(r);
+    if (!current_auth || (strcasecmp(current_auth, NTLM_AUTH_NAME) && strcasecmp(current_auth, NEGOTIATE_AUTH_NAME))) {
+        return DECLINED;
+    }
+#endif
 
     /* Trust the authentication on an existing connection */
@@ -1040,4 +1052,21 @@
 }
 
+#ifdef APACHE22
+static authn_status check_ntlm_winbind_plaintext(request_rec *r,
+                                                 const char *user,
+                                                 const char *password)
+{
+    ntlm_config_rec *conf = ap_get_module_config(r->per_dir_config,
+                                                 &auth_ntlm_winbind_module);
+    return
+        winbind_authenticate_plaintext(r, conf, user, password) == OK
+            ? AUTH_GRANTED : AUTH_DENIED;
+}
+
+static const authn_provider authn_ntlm_winbind_provider = {
+    &check_ntlm_winbind_plaintext,
+    NULL
+};
+#endif
 
 static void register_hooks(apr_pool_t *pool)
@@ -1045,4 +1074,7 @@
     ap_hook_pre_connection(ntlm_pre_conn,NULL,NULL,APR_HOOK_MIDDLE);
     ap_hook_check_user_id(check_user_id,NULL,NULL,APR_HOOK_MIDDLE);
+#ifdef APACHE22
+    ap_register_provider(pool,AUTHN_PROVIDER_GROUP,"ntlm_winbind","0",&authn_ntlm_winbind_provider);
+#endif
 };
 

変更したところは,

  • mod_auth_basic の authn バックエンドとして使えるようにした(あまりメリットはないです)
  • デフォルトの plaintext 認証ではなぜかドメイン名つきだと認証できなかったので(smb.conf の設定のせいかもしれません)ドメイン名なしのユーザ名でも winbind 認証に投げるようにした
  • AuthType 設定がなされているかチェックするようにした

の以上です。

winbindd の設定

winbindd の設定については他のリソースを参照するか手前味噌ながら【 winbind で Linux の認証を ActiveDirectory にまかせる - daily dayflower】を参照してください。

Apache の設定(NTLM 認証編)

先ほど述べた NTLM 認証の流れをみていただければわかりますが,keep-alive 接続がサポートされていることが前提になっています。ですから KeepAlive は必ず On にしておいてください。RedHat 系の httpd.conf だとデフォルトで Off と書いてあるのではまりました。

モジュール自身の設定については README に書いてありますが,間違い*4もあるので書いておきます。

デフォルト設定は以下のようになっています。

NTLMAuth      off    # NTLM認証を有効にする
NegotiateAuth off    # SPNEGO認証を有効にする
NTLMBasicAuth off    # (モジュール内蔵)Basic認証を有効にする

NTLMBasicAuthoritateive on  # (実は使われていない)
NTLMBasicRealm "REALM"      # モジュール内蔵Basic認証の場合のレルム

NTLMAuthHelper "ntlm_auth --helper-protocol=squid-2.5-ntlmssp"
NegotiateAuthHelper "ntlm_auth --helper-protocol=gss-spnego"
PlaintextAuthHelper "ntlm_auth --helper-protocol=squid-2.5-basic"

ですから NTLM 認証のみを行う場合の典型的なコンフィグ(.htaccess とか <Location> 内とか)は以下のようになります。

Require valid-user

AuthType NTLM

NTLMAuth on
NTLMAuthHelper "/usr/bin/ntlm_auth --helper-protocol=squid-2.5-ntlmssp"

これだけです。実は一番最後の行も先に挙げたデフォルトの通りなので必要ありません。

では認証してみます。ドメインにログオンした WindowsIE からアクセスすると,ダイアログなしで認証されます。ログを抜粋すると,

mod_auth_ntlm_winbind.c: doing ntlm auth dance
mod_auth_ntlm_winbind.c: Launched ntlm_helper, pid 14277
mod_auth_ntlm_winbind.c: creating auth user
mod_auth_ntlm_winbind.c: parsing reply from helper to YR TlRM...

libsmb/ntlmssp.c:debug_ntlmssp_flags(63)
  Got NTLMSSP neg_flags=0xa208b207

mod_auth_ntlm_winbind.c: got response: TT TlRM...
mod_auth_ntlm_winbind.c: sending back TlRM....

mod_auth_ntlm_winbind.c: doing ntlm auth dance
mod_auth_ntlm_winbind.c: Using existing auth helper 14277
mod_auth_ntlm_winbind.c: parsing reply from helper to KK TlRM...

libsmb/ntlmssp.c:ntlmssp_server_auth(672)
  Got user=[dayflower] domain=[HOGE] workstation=[WINDOWS] len1=24 len2=24
libsmb/ntlmssp_sign.c:ntlmssp_sign_init(338)
  NTLMSSP Sign/Seal - Initialising with flags:
libsmb/ntlmssp.c:debug_ntlmssp_flags(63)
  Got NTLMSSP neg_flags=0xa2088205

mod_auth_ntlm_winbind.c: got response: AF dayflower
mod_auth_ntlm_winbind.c: authenticated dayflower
mod_auth_ntlm_winbind.c: retaining user dayflower
mod_auth_ntlm_winbind.c: keepalives: 2

のようになっていました(YR だの TT だの KK だの AF だのが気になる方は,【Squid NTLM authentication project】の【The Squid-NTLM helper protocol】参照)。また,$ENV['REMOTE_USER'] に dayflower, $ENV['AUTH_TYPE'] が NTLM となっていました。

実は Firefox 2 (for Linux) も NTLM 認証に対応しています。この場合認証ダイアログが出るので,ドメインのユーザ名とパスワードを入力すると認証されます。

応用編(fallback として Basic 認証も使う)

このままだと例えば Opera (for Linux) ではアクセスできなくなってしまうので,Basic 認証も付け加えてみましょう。この場合,mod_auth_ntlm_winbind 組み込みの Basic 認証メカニズムを使う必要があります*5

Require valid-user

AuthType NTLM

NTLMAuth on
NTLMBasicAuth on
NTLMBasicRealm "Input Windows ID / Password"

NTLMAuthHelper "/usr/bin/ntlm_auth --helper-protocol=squid-2.5-ntlmssp"
PlaintextAuthHelper "/usr/bin/ntlm_auth --helper-protocol=squid-2.5-basic"

これで,

認証できるようになりました。

応用編(任意のグループだけアクセス許可する)

man ntlm_auth の内容から,

NTLMAuthHelper "/usr/bin/ntlm_auth --helper-protocol=squid-2.5-ntlmssp
                --require-membership-of='Domain Admins'"

みたいにすればいけるのではないかなぁと思うのですが,ためしていません。

おまけ(ドメインアカウントBasic 認証だけ使う)

AuthBasicProvider として登録するようにソースを変更したので,Basic 認証のみを使う場合,

Require valid-user

AuthType Basic
AuthName "Input ID / Password"
AuthBasicProvider file ntlm_winbind

AuthUserFile "/foo/bar/.htpasswd"
PlaintextAuthHelper "/usr/bin/ntlm_auth --helper-protocol=squid-2.5-basic"

のようにすることもできます。この例だと,

  • .htpasswd にユーザがいれば OK
  • いなければ NTLM にユーザがいれば OK

のようになります。

積み残し

SPNEGO 認証もいけるはず,なんですがどうしてもうまくいきません。Kerberos のチケットをうまく validate できてないみたいなので(私的環境の AD / )DNS まわりの問題かなぁと思ったりしていますが,検証めんどくさい……

keep-alived な connection では一度認証したら二度としないという点で NTLM 認証はあやういな,と思いました。サーバ側の実装を間違えると。

*1:win32 の場合,素直に IIS を利用するか mod_auth_sspi を利用してください

*2:もともとは Squid で利用するために開発されたものです

*3:通信路をドメインパスワードが解読可能な形で流れるのであんまりおすすめできない

*4:AuthType は複数指定しても無効で一番最後の指定が有効になる;のに複数指定した例がある

*5:事実上「認証ハンドラ」モジュールは単一であることしか考慮されていない気がします……Apacheのコードが

*6:network.automatic-ntlm-auth.trusted-uris に適切な URI(一部で可;たとえば http:// とかでも OK)を指定する

sanosano 2007/11/17 20:59 はじめまして、sano と申します。
今、mod_auth_ntlm_winbind を動作させようとしているのですが、
どうしてもうまくいきません。

以下のように、tdb_open_ex() が secrets.tdb を開こうとして、permission denied が発生する
現象に悩んでおります。

このような現象に遭遇したことは、これまでにございませんでしたでしょうか?

[Sat Nov 17 20:24:08 2007] [debug] mod_auth_ntlm_winbind.c(652): [client 172.29.11.112] creating auth user
[2007/11/17 20:24:17, 10] utils/ntlm_auth.c:manage_squid_request(2083)
Got ’YR YIIE9QYGK[...]
8tdb(unnamed): tdb_open_ex: could not open file /usr/local/samba3.0.27/private/secrets.tdb: Permission denied
[2007/11/17 20:24:17, 0] passdb/secrets.c:secrets_init(67)
Failed to open /usr/local/samba3.0.27/private/secrets.tdb
[2007/11/17 20:24:17, 10] lib/server_mutex.c:grab_server_mutex(42)
grab_server_mutex: failed for replay cache mutex
[2007/11/17 20:24:17, 1] libads/kerberos_verify.c:ads_verify_ticket(384)
ads_verify_ticket: unable to protect replay cache with mutex.
[Sat Nov 17 20:24:40 2007] [debug] mod_auth_ntlm_winbind.c(703): [client 172.29.11.112] parsing reply from helper to YR YIIE9QYGKw[...]
[Sat Nov 17 20:25:05 2007] [debug] mod_auth_ntlm_winbind.c(741): [client 172.29.11.112] got response: NA oRQwEqADCgECoQsGCSqGSIL3EgECAg== NT_STATUS_LOGON_FAILURE
[Sat Nov 17 20:25:06 2007] [debug] mod_auth_ntlm_winbind.c(821): [client 172.29.11.112] user not authenticated: NT_STATUS_LOGON_FAILURE

Apache は nobody で実行されます。
ですので、ntlm_auth も nobody の権限で動作します。
その ntlm_auth が、root 権限でないとアクセスできない、secrets.tdb をオープン
しようとして、permission denied が発生します。

% ls -l /usr/local/samba3.0.27/private/
-rw------- 1 root root 8192 Nov 17 20:22 secrets.tdb
-rw------- 1 root root 0 Nov 17 19:19 smbpasswd

もしよろしければ、dayflower さんがどのような環境で動作確認されたか
教えて頂けませんでしょうか?
(ソフトウェアのバージョンの組み合わせの問題なのかもしれませんので)

ちなみに、私の環境は以下の通りです。
- samba-3.0.27
- mod_auth_ntlm_winbind (CSVの最新のコード)
- krb5-1.6.2 (MIT)
- httpd-2.0.61

よろしくお願いいたします。

dayflowerdayflower 2007/11/19 13:47 こんにちは。

この記事を書いたのがだいぶまえなのでうろ覚えですが,
httpd ⇒ (spawn) ntlm_auth ⇒ (communicate with pipe) winbindd
という経路をたどって認証しているんだと思います。なので secrets.tdb を winbindd が読み込むことができれば大丈夫なのではないでしょうか。ntlm_auth のソースを読み込んでないのではずしているかもしれませんが。

一応諸元を。
- Fedora core 6
- Samba 3.0.24(-7.fc6)
- httpd-2.2.6(-1.fc6)
krb5 とかは FC6 についてるやつなのでちとわかりません。

sanosano 2007/11/19 16:25 お返事ありがとうございます。

とても奇妙なのですが、winbindd が secret.tdb をオープンしようとしている
のではなく、ntlm_auth が secret.tdb をオープンしようとしているようなんです。
ntlm_auth は、Apache と同じく nobody の権限で動作しているため、root しか読み書き
が許されていない secrets.tdb をオープンしようとして、エラーが発生するようです。

以下は、GDB で secret.tdb を開こうとした時のバックトレースです。

(gdb) bt
#0 tdb_open_ex (name=0xbffc5d90 ”/usr/local/samba3.0.27/private/secrets.tdb”, hash_size=0, tdb_flags=0,
open_flags=66, mode=384, log_ctx=0xbffc5d40, hash_fn=0) at tdb/common/open.c:153
#1 0x00dbe0cb in tdb_open_log (name=0xbffc5d90 ”/usr/local/samba3.0.27/private/secrets.tdb”,
hash_size=0, tdb_flags=0, open_flags=66, mode=384) at lib/util_tdb.c:683
#2 0x00d13f58 in secrets_init () at passdb/secrets.c:64
#3 0x00d164b4 in secrets_named_mutex (name=0x85b81b8 ”replay cache mutex”, timeout=10)
at passdb/secrets.c:925
#4 0x00d1800c in grab_server_mutex (name=0xdcd854 ”replay cache mutex”) at lib/server_mutex.c:41
#5 0x00d13754 in ads_verify_ticket (mem_ctx=0x85b7a18,
realm=0x85b7ac8 ”ADTEST.WIN2K301.DEV”, time_offset=0, ticket=0xbffc677c,
principal=0xbffc62e8, pac_data=0xbffc62b0, ap_rep=0xbffc62d0, session_key=0xbffc62c0)
at libads/kerberos_verify.c:383
#6 0x00cfe04e in manage_gss_spnego_request (stdio_helper_mode=GSS_SPNEGO,
buf=0xbffc67f0 ”YR YIIE9QYGKwYBBQUCoIIE6TCCBOWgJDAiBgkqhkiC9xIBAgIGCSqGSIb3EgECAgYKKwYBBAGCNwICCqKCBLsEggS3YIIEswYJKoZIhvcSAQICAQBuggSiMIIEnqADAgEFoQMCAQ6iBwMFACAAAACjggO+YYIDujCCA7agAwIBBaEiGyBBRFRFU1QuV0lOMkszMDEuR”..., length=1703) at utils/ntlm_auth.c:1170
#7 0x00d01177 in manage_squid_request (helper_mode=GSS_SPNEGO, fn=0xcfd89d <manage_gss_spnego_request>)
at utils/ntlm_auth.c:2091
#8 0x00d011ca in squid_stream (stdio_mode=GSS_SPNEGO, fn=0xcfd89d <manage_gss_spnego_request>)
at utils/ntlm_auth.c:2100
#9 0x00d01851 in main (argc=2, argv=0xbffc73a4) at utils/ntlm_auth.c:2321

dayflowerdayflower 2007/11/20 16:05 おお,バックトレースまでありがとうございます。
拝見させてわかったんですが,sano さんは SPNEGO 認証に挑戦しようとなさっている(あるいはそのような設定になっている)ような。

上記日記をご覧になるとおわかりになると思いますが,私は SPNEGO 認証は断念したクチなので,対処法についてはなんともかんともです。というか逆にこのスタックトレースのおかげですこし手がかりを得ることができてありがとうございます。

もしSPNEGOじゃなくてNTLM認証をしたいということであれば,NegotiateAuthや--helper-protocol等,上記日記の設定をご参照下さい。

dayflowerdayflower 2007/11/20 16:07 あとSPNEGOの場合,拙著ながら
http://d.hatena.ne.jp/dayflower/20070712/1184220520
が参考になるかもです。

sanosano 2007/12/07 16:45 お返事が遅くなり、すみませんでした。
ネバってみたものの結局ダメでした。
わざわざ一緒にお付き合いくださり、ありがとうございました。

それでと言っては何ですが、mod_spnego に朗報があるので
手前味噌になりますが、ご紹介させてください。

実は、わけあって、これまでこちらから mod_spnego の作者に
アプローチをかけてきました。

その結果、

○ライセンスが GPL から Apache License に
○Windows 環境では SSPI 対応になり、Kerberosライブラリが不要に
○Linux、Windows 環境両方において fbopenssl と openssl が不要に

と、結構良い感じのモジュールへと進化しました。

また機会があれば試してみてください^^

dayflowerdayflower 2007/12/07 17:27 mod_spnegoはぜんぜん試してなかったのですが,いろいろ前提ライブラリとか必要だったんですね。Windows だと SSPI 経由ですか。かっこいい。

いつか暇ができたら試してみたいです。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証