レベル: 中級 Barry Brachman (brachman@dss.ca), President, Distributed Systems Software, Inc.
2006年 12月 13日 Web サーバーでは、アプリケーションを対象にユーザー認証を行ったり、簡単な許可検査を行うことができます。しかし、Web サービスやサービス指向アーキテクチャー(SOA) の開発者は、ユーザーの ID に基づいてシステムの特定機能へのアクセスを制限するために、カスタム・コードを作成したり、動作や外観をカスタマイズしなければならないことがよくあります。アプリケーションに許可検査を組み込むと、柔軟性が失われて誤りが起こりがちになり、アプリケーション自体、複雑になります。プログラム・ロジックによる許可検査を行う代わりに、データを基にした許可検査を行ったらどうでしょうか。許可フレームワークを再利用すると、スクリプトやコンパイル済みのプログラムを小さく簡単でより安全なものにすることができ、アプリケーションの開発にかかる時間と労力を削減できます。
はじめに
これを読んでいる方々は、オペレーティング・システムに備わっているアクセス制御機能についてよくご存じでしょう。一般にこの機能では、ファイル・システムで指定されたリソースに関してのシステム・コールと操作要求に対して、許可検査を行います。通常、プログラマーは、データ・ファイルの読み取りやシステム時刻の設定を行う権限がプログラムを実行するユーザーにあるかどうか、テストする必要はありません。この確認はオペレーティング・システムで行われ、ユーザーの操作が許可されるか拒否されるかを示す戻り値によってプログラムに通知します。例えばUNIX® の場合は、ファイルの所有者 ID、グループ ID、および (ユーザーの実際の ID または有効な ID に関する) アクセス権に応じて設定された要件を施行することになっています。これはシンプルな設計ですが、十分でない場合もあるため、一部のUNIX 型システムではアクセス制御リストや他の仕組みを通じてこの機能が拡張されています。他のオペレーティング・システムでは、UNIX よりも複雑なセキュリティー・モデルを使用しています。このセキュリティー・モデルも通常はやはり、システムが認識する(/etc/passwd または NIS に表示される) ファイル・オブジェクトとユーザー・アカウントに適用されます。
サーバー・ベースのアプリケーションによっては、これはふさわしいモデルです。サーバーは、最初は強化された特権を使用して実行されるため、どのような操作でも実行でき、特定のユーザーを代行しているときは、事実上そのユーザーになり代わっています。あるいは、サーバーはシステム・コールを使用して、そのユーザーに許可された操作しかできないように制限するセキュリティー・モデルを構築することもできます。プログラムでは、その操作を実行する前に、「このユーザーはその操作を実行してもよいか」といった、権限のテストをしておく必要があります。
Web サービスの許可検査
プログラムでは、プログラムが管理しているリソースと、オペレーティング・システムでは認識されないユーザー・アカウントに対して、プログラム自体が持っているアクセス制御要件を適用しなければならないことがあります。その主な例がApache Web サーバーです。Apache Web サーバーは、このサーバーのリソースに対して HTTP 要求があれば、要求を許可または拒否するようにサーバーを構成できます。専用のパスワード・ファイルに格納された項目によって、Webサーバーのアカウントが作成され、このアカウントはオペレーティング・システムが認識するアカウントとは完全に切り離されます。同様に、Web サーバーで使用するグループ・メンバー・リストも具体的に作成されます。Apache管理者は、こうした名前とサーバーの Require、Allow、Deny ディレクティブを使用して、リソースにアクセスできる (またはアクセスできない)ユーザーを指定できます。オペレーティング・システムではこのような指定ができないため、Apache のプログラマーは独自の許可サブシステムを使用できるようにしています。
Web サーバーとオペレーティング・システムのユーザー・リストは統合することもできますが、たいていは、セキュリティー、パフォーマンス、実施という観点から、この2 つは分けて保管されています。
Apache のような Web サーバーに備わっている許可機能は、セキュリティーの外層しか提供していないため、簡単なアクセス制御と言えるでしょう。そこでは、許可検査の結果によって要求を許可するかどうかが判断されます。Webサービスへのアクセスが拒否されると、例えば Web サービスは Web サーバーによって実行されないため、Web サービス側では要求が発生しません。
しかし、多数あるサーバー・ベースのアプリケーション (Web ベースのアプリケーションも含まれます) には、この検査をはるかに上回る許可試験機能が必要です。Webサービス・アプリケーションとして、誰もが実行できるけれども、ユーザーにさまざまな権限があることを認識するアプリケーションを考えてみましょう。おそらくアプリケーションの一番簡単な例として、特定のユーザーだけが管理機能にアクセスでき、それ以外のユーザーはその機能を実行できないどころか、機能自体が表示されない(メニューに表示されないか、少なくとも選択できない) アプリケーションが考えられます。また、よくある例としては、各ユーザーのプロファイルを格納したアプリケーションがあります。プロファイルは、所有者もしくはアプリケーションの管理者だけが変更できます。多くの場合、アプリケーション側で、特定ユーザーによるデータへのアクセスを制限しなければなりません。このような許可試験は、きめの細かいアクセス制御と言えます。というのも、実行中のプログラムによって、そのプログラムと連携する、事実上あらゆる種類のリソースにこの試験が行われるからです。
才能あるプログラマーが、巧みな手段でこうした問題を回避できる場合もあります。例えば、アプリケーションでリソースの指定に URI を使用し、そのURI に関する Web サーバーのアクセス制御を設定しておきます。その後で HTTP 要求を発行すれば、ユーザーに権限があるかどうかを判断できます。ただ、これはWeb サーバーの許可検査の仕組みを利用した手法であり、実用には向きません。
この点では、Perl などのプログラミング言語のほとんどが役に立ちません。基礎となるシステムに備わる仕組みの上に、簡単な機能レイヤーが重ねられるだけです。Java™言語であれば、許可フレームワークが組み込まれています。ただ、当然のことながら Perl や C/C++ のプログラマーには役に立ちません。
プログラマーが直面する難題には、2 つの要素があります。第一に、こうしたアプリケーションのユーザーには基礎となるシステムのアカウントが不要です。第二に、オブジェクトの種類とオブジェクトへのアクセス方法は、オペレーティング・システムのセキュリティー・モデルの設計目的とはかなり異なる場合があります。したがって、プログラマーはアプリケーション固有の許可試験に対応するため、大量のコードを作成しなければならなくなり、ときには本質的に同じフレームワークを別の言語で実装し直す必要に迫られます。Oracleや MySQL などのデータベース・システムでは、システムおよびオブジェクト・レベルの操作について、ユーザー・アカウント、ロール、特権が管理されています。
きめの細かい許可検査
きめの細かいアクセス制御では、プログラムの実行権限やデータ・ファイルの読み取り権限を許可または拒否する以上のことを行います。CGI プログラムとして実装されたWiki サーバーを考えてみましょう。このサーバーでは、一連の Web ページをユーザーごとに 1 つずつ維持しています。どのユーザーもすべてのWeb ページに対する読み取りアクセス権があります。また、どのユーザーも自分のページにコンテンツを追加したりページを管理できますが、他人のページに対しては行えません。管理者として指定されたユーザーであれば、どのページに対しても更新と管理が可能です(例えば、攻撃的なコンテンツや違法コンテンツを削除できます)。Web ページを所有者または管理者に表示する場合、メニューまたはリンクですべての操作を選択できるようにし、それ以外のユーザーに対しては、この操作が表示されないようにするか、選択できないようにする必要があります。さらに、Webページは誰もが表示できるものの、ページ・コンテンツの更新などの実行制限された操作はページの所有者か管理者だけが実行できるように、アプリケーションで保証する必要があります。
ここでは、許可に関するロジックをアプリケーションに組み込みます。コード内の適切な場所に、現在のユーザーに操作を実行する権限があるかどうかをテストするプログラム・ロジックをプログラマーが記述します。
メニューを作成する場合、リスト 1 のようなコードになります。
リスト 1. 許可された操作のメニューを作成する -- 間違った方法
for (op = 0; op < n_operations; op++) {
if (op == CREATE_OP && is_admin(current_user))
add_operation_to_menu(menu, op);
else if ((op == UPDATE_OP || op == DELETE_OP)
&& (is_owner(current_page, current_user) || is_admin(current_user)))
add_operation_to_menu(menu, op);
else
add_operation_to_menu(menu, op);
}
|
一見したところ、リスト 1 は正しいように見えますが、バグがあります。このコードでは、バグを見つける前に、バグの箇所が実行されてしまい、この種のテストを作成するときに、うっかりしたミスを簡単に犯してしまうことを示しています。次のリスト2 が正解です。
リスト 2. 許可された操作のメニューを作成する -- 正しい方法
for (op = 0; op < n_operations; op++) {
if (op == CREATE_OP && is_admin(current_user))
add_operation_to_menu(menu, op);
else if ((op == UPDATE_OP || op == DELETE_OP)
&& (is_owner(current_page, current_user) || is_admin(current_user)))
add_operation_to_menu(menu, op);
else if (op != CREATE_OP && op != UPDATE_OP && op != DELETE_OP)
add_operation_to_menu(menu, op);
}
|
コード内の別の場所で、操作のディスパッチ時または各操作の開始時に、似たようなテストが行われます。
リスト 3. 実行制限された操作のテスト
if (op == CREATE_OP && !is_admin(current_user))
raise_exception("Operation not permitted")
else if ((op == UPDATE_OP || op == DELETE_OP)
&& !(is_owner(current_page, current_user) || is_admin(current_user)))
raise_exception("Operation not permitted")
|
上の 2 つのコードによって実行される許可検査は、プログラムの別の場所で記述される可能性がありますが、密接に関係します。さらに高度な場合、許可試験は非常に複雑になる可能性があります。例えば、(ページの所有者が定義した)あるグループ・メンバーが前は制限されていた操作を実行できるように、アプリケーションを後で拡張する場合、プログラマーはこの検査が実行されるすべてのコード部分を見つけて検討し、場合によっては変更する必要があります(2 箇所にとどまらず、多数あるのは明らかです)。適切に変更を行わないと、結果として過失または故意によるアプリケーションの誤用が発生します。
ルール・ベースの許可検査
プログラマーの負担を軽減し、アプリケーションのセキュリティーを高めるには、許可検査のために設計された専用のフレームワークを使用することをお勧めします。プログラマーが、毎回ルーチンを実装し直さずに、数学、暗号、ネットワーク、データベースの関数ライブラリーを使用するのと同じように、目の前のタスクにのみ関連する問題に対しては大半が解決されるパッケージを利用してみてはいかがですか。
次のことが可能なルール・ベース (データ駆動型) の許可フレームワークをお勧めします。
- コマンド・ラインから呼び出すことができるため、事実上すべてのスクリプトから呼び出したり、任意のプログラムで実行できます。
- シンプルな C 言語 API であるため、直接呼び出して、さまざまなスクリプト言語の拡張機能として組み込むことができます。
- 汎用のユーザー命名構文を使用するため、ほとんどの認証方式と相互運用できます。
- すべてのリソースに適用できるルールをサポートします。
- 簡単な XML 文書でアクセス制御を表現します。
- より複雑な意志決定を支援するために、変数、式、および制御フローを提供します。
- アクセス制御要件 (ユーザーの IP アドレス、日付と時刻、指定リストにユーザーの名前があるかどうかなど) をテストする一連の関数が豊富に含まれています。
このフレームワークで提供される関数は、もちろん組み込む必要がありませんが、その他にも次のように利点が多数あります。
- アプリケーションで使用するルールは、権限を持つユーザーであれば、アプリケーションの変更や再コンパイルをせずに変更することができます。そのためセキュリティー・ポリシーの管理を、プログラマーでない人やアプリケーションの実装言語に不慣れな人に委任できます。
- ルールを変更した場合、アプリケーションまたは関連する一連のアプリケーション全体で出現するすべての個所について、自動的に変更後のルールが使用されます。つまり、コードを変更する必要はなく、すべての個所で同期がとられます。
- ルールとプログラミング言語は無関係なので、同じルールを別のアプリケーションで使用することができます。
- フレームワークに適用された改善点とバグ修正は、そのフレームワークを使用するアプリケーションに役立ちます。アプリケーションの変更や再コンパイルは不要です。
もちろん、いくつか欠点もあります。学習にかかるオーバーヘッドのほか、フレームワークにバグがあると、フレームワークを使用するすべてのアプリケーションがそのバグを継承します。この方法で許可検査を実行すると、インライン・コードよりもやや処理が遅くなります。特に、別のプロセスから呼び出された場合に顕著です。
DACScheck: 許可フレームワーク
このような観察の結果が dacscheck の設計と実装を促進したのであればよかったのですが、実際には違います。正確には、Apache 許可モジュールに代わる豊富な代替機能が DACS の開発につながっています。DACS とは、軽量のシングル・サインオンを実現するシステムです。許可フレームワークが汎用化され、ほぼすべてのアプリケーション(Web ベースまたは非 Web ベース) に採用されたと実感したのは、かなりたってからでした。
dacscheck の使用方法を説明するために、メニューを作成する疑似コード リスト 2を見てみましょう。今回は Perl です。
リスト 4. Perl から DACScheck を使用する
use DACScheck.pm;
# Tell dacscheck which rules to use
dacscheck_rules("/usr/local/wiki_app/acls");
# Build a list of operations for the menu.
for ($op = 0; $op < $n_operations; $op++) {
# The object of interest, which is used to locate the appropriate rule,
# is arbitrarily named "/<username>/menu".
my $object = "/$ENV{'DOCUMENT_ROOT'}/menu";
my $result = dacscheck_cgi($object);
# Access to the object is granted only if the returned value is one.
if ($result == 1) {
add_operation_to_menu($menu, $op);
}
}
|
Perl モジュール DACScheck.pm は、dacscheck_cgi() を含む dacscheck に簡素化したインターフェースを提供します。オブジェクトの命名以外に、この関数は dacscheck に対し、CGI コンテキスト内で実行すること、および要求を作成するユーザーの ID として REMOTE_USER の値を使用しなければならないことを通知します。Apacheは、ユーザーが認証を受けると、自動的に REMOTE_USER を設定します。
dacscheck が管理する各リソースには、対応するルールがあります。ルールはさまざまな方法で保管およびアクセスが可能ですが、通常、各ルールは通常のテキスト・ファイルに保管され、関連リソースのルールは共通のルート・ディレクトリーに配置されます。ルールはXML 文書で表され、自由な形式の C 言語のような式も入れることができます。式は、実行コンテキストからインスタンスを生成した変数を参照できます。これには、環境変数、Webサービス引数、およびストリング処理や高度な ID テスト向けのさまざまな組み込み関数が含まれます。Tcl (ツール・コマンド言語) などの既存の拡張言語は、この機能が複雑にならないように避けられてきました。より複雑なテストは、別個のプログラムから実行でき、HTTP経由で呼び出されるプログラムからも可能です。また、ルールは別のルールを指し示すこともできます。これによって、管理者はルールの責任を委任できます。
では、どのようなルールになるかを見てみましょう。このルールでは、要求を許可するべきか、拒否するべきかの決定方法を dacscheck に通知します。ユーザーは既にログインしているため、REMOTE_USER は設定済みであることを想定しています。また、OP は操作を選択する引数で、PAGEは Wiki ページの所有者を識別する引数です。
リスト 5. dacscheck ルール・ファイルの例
<acl_rule>
<services>
<service url_expr="/${Args::PAGE}/menu"/>
</services>
<rule order="allow,deny">
<precondition>
<predicate>
${Args::OP} eq "CREATE_OP"
</predicate>
</precondition>
<allow>
dacs_admin()
</allow>
</rule>
<rule order="allow,deny">
<precondition>
<predicate>
${Args::OP} eq "UPDATE_OP" or ${Args::OP} eq "DELETE_OP"
</predicate>
</precondition>
<allow>
dacs_admin() or ${Args::PAGE} eq ${Env::REMOTE_USER}
</allow>
</rule>
<rule order="allow,deny">
<allow>
user("any")
</allow>
</rule>
</acl_rule>
|
このルールは詳しく記述されていますが、これは説明しやすいようにするためです。また、構文が十分わかるようになれば、解読して変更するのが簡単になるように、セキュリティー・ポリシーを示すためでもあります。
service 要素は、ルールの適用先リソースを dacscheck に指示します。dacscheck_cgi() に対する引数は、このストリングと一致します。
このアクセス制御ルールは、次の 3 つのルール要素から成ります。1 つ目は OP 引数が CREATE_OP である場合にのみ選択されます。2 つ目は OP 引数が UPDATE_OP または DELETE_OP である場合にのみ選択されます。3 つ目はこのどちらでもない場合にのみ選択されます。order 属性は、Apache の Order ディレクティブと同じ目的を果たします。したがって allow 要素と deny 要素が評価される際のデフォルトの動作と順序を設定します。
この例で、最初のルールは dacs_admin() 関数が「True」を返す場合にのみアクセス権を与えます。この関数はユーザーの ID を確認します。2つ目のルールは、管理者と Wiki ページの所有者にアクセス権を与えます。3 つ目のルールは、全員にアクセス権を与えます。これは、実行制限された操作が要求されていないためです。
Wiki のアクセス制御ポリシーを変更するときに必要な作業は、ルールを変更することだけです。変更はすぐに、ルールを参照するすべての対象に適用されます。管理者が特定のIP アドレス範囲からの要求に対して、ページへのアクセスを拒否しなければならない場合、次のようなコードを追加できます。
リスト 6. アクセスを拒否する追加の rule 節
<rule order="allow,deny">
<precondition>
<predicate>
from("10.0.0/24")
</predicate>
</precondition>
</rule>
|
allow,deny の順序はデフォルトでアクセスを拒否するため、この範囲内の IP アドレスからの要求はすべて拒否されます。
この形式の許可検査を適用できる状況は、たくさんあります。例えば、ftp デーモン (ftpd) はこの機能を利用できます。ftpd を拡張すれば、ユーザー ID やコンテキスト情報に基づいて、操作 (put, get, cd, など) を許可または拒否するルールを使用することができます。こうしたユーザー ID やコンテキスト情報は、ルールによるテストが可能です。また、別の例として、特定の時間中にのみアップロードを許可したり、ディレクトリーへのアクセスを特定ユーザーまたは特定IP アドレスから接続しているユーザーに制限することができます。
まとめ
柔軟な許可フレームワークを使用すれば、セキュリティーを高め、プログラマーの「重労働」の多くを行うことができます。また、スクリプトとコンパイル済みのプログラムを小さく簡単なものにすることができ、アプリケーションの開発にかかる時間と労力も削減できます。dacscheck には未解決の事項があるものの、これが有益なツールであることは既に証明されています。効率性についてはあまり注目されていませんが、dacscheck を普通に使用した場合、パフォーマンスに重大な影響はありません。
dacscheck の姉妹プログラムとして dacsauth があります。dacsauth は dacscheck が許可に対して役立つのとほぼ同じように認証に役立ちます。dacsauth によってプログラマーは既存の認証方式を利用できるようになります。これにより、新しいアプリケーションでその独自のユーザー・リストが必要になるたびに、認証方式を実装し直さなくて済みます。
関連する別のプログラムとして dacstransform があります。これは、ルール・ベース文書を変換するためのユーティリティーです。dacscheck と同じルールを使用して、マークアップ文書内でテキストを編集、挿入、置換することができます。各変換は実行時に評価されるルールに依存して、特定ユーザーまたはコンテキストに合わせたマークアップ文書から新規文書を生成することができます。
参考文献 学ぶために
議論するために
著者について  | |  | Barry Brachman は Distributed Systems Software の創立者で社長を務めています。この会社では、ソフトウェア開発およびコンサルティングを行っています。コンピューターに関しては、UNIX カーネル、コンパイラー、分散システム、プロトコル検証から、システム性能評価、PKI、X.500/LDAP に至るまで、彼には幅広い経験があります。また、オペレーティング・システムとコンピューター・ネットワークについて講演を行っています。ブリティッシュコロンビア大学ではコンピューター・サイエンスの理学修士号と博士号を取得しました。 |
記事の評価
|