Java GSS-APIおよびJAASで実行可能な他の操作
前のチュートリアル、「JAAS LoginユーティリティおよびJava GSS-APIを使用したセキュアなメッセージ交換」では、2つのアプリケーション(特にクライアントとサーバー)がJava GSS-APIを使用して相互間のセキュリティ保護されたコンテキストを確立して、メッセージを安全に交換する方法を示しました。
コンテキスト・イニシエータ(このクライアント/サーバーの例ではクライアント)を使用してコンテキストを確立したあとで、コンテキスト・アクセプタ(サーバー)が実行可能な操作はほかにもあります。基本的に、サーバーはクライアントを「装う」ことができます。偽装のレベルは、クライアントがクレデンシャルをサーバーに委譲しているかどうかにより異なります。
クライアント・ユーザーでのコードの実行
サーバーがクライアントを装う1つの方法は、クライアント・コードを実行するのと同じエンティティ(ユーザー)でコードを実行することです。通常、スレッドにより実行されるメソッドは、そのスレッド自体のアクセス制御設定を使用します。ただし、このチュートリアルでは、サーバーがクライアントを装う際、クライアントのアクセス制御設定を使用するため、サーバーはクライアント自体が実行時に保持する、厳密に同じリソースにアクセスできます。
このチュートリアルで使用する方法の主要な利点は、JAAS承認コンポーネントをアクセス制御に使用できることです。JAAS承認コンポーネントがない場合、サーバー・プリンシパルは、クライアント・ユーザーの代わりに実行されるコードがアクセスするすべてのリソースにアクセスできる必要があり、サーバー・コードには、ユーザーがそのリソースにアクセスすることが承認されているかどうかを判別するためのアクセス制御ロジックを含める必要があります。JAAS承認を利用することにより、プリンシパルベースのアクセス制御が提供されるため、アクセス制御が自動的に処理されます。これらのコード内のセキュリティ関連操作のアクセス権は、そのユーザーにのみ付与する必要があり、サーバー・コードに付与する必要はありません。JAAS承認の詳細は、「JAAS承認」チュートリアルを参照してください。
基本的なアプローチ
サーバーは、どのようにしてクライアントを「装い」、クライアント・コードを実行するユーザーに代わってコードを実行できるのでしょうか。基本的に、そのユーザーに代わってクライアント・コードを実行するように設定する方法と同じです。サーバー・コードで認識する必要があるのは、ユーザーのプリンシパル名のみです。これは、クライアントで確立されたコンテキストから取得できます。
クライアント・コードを実行するユーザーのJAAS認証により、ユーザー(プリンシパル)名を保持するプリンシパルを含むサブジェクトが作成されることはすでに学びました。その後、プリンシパルは(LoginユーティリティからのSubject.doAsPrivileged
呼出しを介して)新しいアクセス制御コンテキストに関連付けられ、クライアント・コードがそのユーザーに代わって実行されるものと見なされます。以降のアクセス制御は、クライアント・コードを実行する特定のユーザーに必要なアクセス権が付与されるかどうかに基づいて決定されます。
サーバー・コードも同様に処理されます。ただし、一般に、認証に指定されるプリンシパルはユーザー・プリンシパルではなく「サービス・プリンシパル」である点が異なります。再度、指定されたプリンシパル名のプリンシパルを含むサブジェクトが作成され、Subject.doAsPrivileged
が呼び出されます。サーバー・コードは指定されたプリンシパルにかわって実行されると見なされます。以降のアクセス制御は、サーバー・コードを実行する特定のプリンシパルに必要なアクセス権が付与されるかどうかに基づいて決定されます。
いったんクライアントおよびサーバーが相互コンテキストを確立すると、次のコードでコンテキスト・イニシエータの名前(クライアントのプリンシパル名)を確認できます。
GSSName clientGSSName = context.getSrcName();
コンテキスト・アクセプタ(サーバー)は、この名前を使用して、同じエンティティを表すプリンシパルを含むサブジェクトを生成できます。たとえば、次の方法でOracleのJDKを使用してそのようなサブジェクトを構成できます。
Subject client =
com.sun.security.jgss.GSSUtil.createSubject(clientGSSName, null);
createSubjectメソッドは、引数として指定されたGSSNameおよびGSSCredentialから新しいサブジェクトを作成します。サーバー・コードが単にローカルJVM内でユーザーにかわってコードを実行する場合、ユーザーのクレデンシャルは必要ではありません。実際のところ、クライアントがサーバーにクレデンシャルを委譲しているのでないかぎり、ユーザーのクレデンシャルは取得できません。詳細は、クライアントから委譲されたクレデンシャルの使用を参照してください。ここではクレデンシャルは必要ではないため、GSSCredential引数にnull
を渡します。
ノート:
ノート: Oracleの提供するJDKを使用していない場合、これを実行する別の方法は、次のようにしてKerberosPrincipalインスタンスを生成することです。
KerberosPrincipal principal =
new KerberosPrincipal(clientGSSName.toString());
次に、このプリンシパルを使用して新たなサブジェクトを生成するか、既存のサブジェクトのプリンシパル・セット内にこのプリンシパルを追加します。
サーバーがユーザーとして実行するコードは、java.security.PrivilegedAction
(またはjava.security.PrivilegedExceptionAction
)を実装するクラスのrun
メソッドで開始する必要があります。つまり、コードをこのrun
メソッド内に配置することも、run
メソッドから呼び出すこともできます。
サーバー・コードは、PrivilegedAction (またはPrivilegedExceptionAction)のインスタンスとともにサブジェクトをSubject.doAsPrivileged
に渡し、以降のコードをPrivilegedActionのrun
メソッドから、指定されたサブジェクトのプリンシパル(ユーザー)で開始できます。
たとえば、PrivilegedActionクラスがReadFileActionを呼び出し、引数としてプリンシパル名を保持するStringを取る場合を考えましょう。このクラスのインスタンスは、次のコードで作成できます。
String clientName = clientGSSName.toString();
PrivilegedAction readFile =
new ReadFileAction(clientName);
doAsPrivileged
の呼出しは、次のようになります。
Subject.doAsPrivileged(client, readFile, null);
サンプル・コードおよびポリシー・ファイル
次のサンプル・コードおよびポリシー・ファイルは、クライアントを実行する特定のユーザーのみに許可されるセキュリティ関連操作用のコードを実行するために、サーバーがクライアントを装う方法を示します。
SampleServerImp.java
SampleServerImp.java
ファイルは、クライアントとメッセージを交換した後、クライアント・ユーザーとしてReadFileAction
を実行するための次のようなコードが生成される点を除けば、前のチュートリアル(JAAS LoginユーティリティおよびJava GSS-APIを使用したセキュアなメッセージ交換)で紹介したSampleServer.java
ファイルとまったく同じです。
System.out.println("Impersonating client.");
/*
* Extract the KerberosPrincipal from the client GSSName and
* populate it in the principal set of a new Subject. Pass in a
* null for credentials since credentials will not be needed.
*/
GSSName clientGSSName = context.getSrcName();
System.out.println("clientGSSName: " + clientGSSName);
Subject client =
com.sun.security.jgss.GSSUtil.createSubject(clientGSSName,
null);
/*
* Construct an action that will read a file meant only for the
* client
*/
String clientName = clientGSSName.toString();
PrivilegedAction readFile =
new ReadFileAction(clientName);
/*
* Invoke the action via a doAsPrivileged. This allows the
* action to be executed as the client subject, and it also
* runs that code as privileged. This means that any permission
* checking that happens beyond this point applies only to
* the code being run as the client.
*/
Subject.doAsPrivileged(client, readFile, null);
ReadFileAction.java
ReadFileAction.java
ファイルには、ReadFileAction
クラスが含まれます。このコンストラクタは、クライアント・ユーザー名のStringを引数に取ります。クライアント・ユーザー名は、ReadFileAction
が読取りを試みるファイルのファイル名の作成に使用されます。ファイル名は、次のようになります。
./data/<name>_info.txt
ここで、<name>
は対応するレルムを含まないクライアント・ユーザー名になります。たとえば、ユーザー名全体がmjones@KRBNT-OPERATIONS.EXAMPLE.COM
の場合、ファイル名は次のようになります
./data/mjones_info.txt
ノート:
Windowsの場合、スラッシュをバックスラッシュで置き換えます。
ReadFileAction
のrun
メソッドは、指定されたファイルを読み取り、その内容を出力します。
serverimp.policy
ReadFileActionはファイルを読み取ろうとします。この操作はセキュリティ・チェックの対象になります。ReadFileActionはクライアント・ユーザー(プリンシパル)として実行されると見なされるので、ReadFileActionコード自体のみでなく、クライアント・プリンシパルに対して適切なアクセス権を付与する必要があります。
ReadFileAction
クラスがReadFileAction.jar
という名前のJARファイル内に配置され、ユーザー・プリンシパル名がmjones@KRBNT-OPERATIONS.EXAMPLE.COM
である場合を考えましょう。この場合、ポリシー・ファイル内に次のコードを記述して、アクセス権を付与します。
grant CodeBase "file:./ReadFileAction.jar"
Principal javax.security.auth.kerberos.KerberosPrincipal
"mjones@KRBNT-OPERATIONS.EXAMPLE.COM" {
permission java.io.FilePermission "data/mjones_info.txt",
"read";
};
serverimp.policy
ファイルは、前の(JAAS LoginユーティリティおよびJava GSS-APIを使用したセキュアなメッセージ交換)チュートリアルのserver.policy
ファイルと同じですが、SampleServerコードにdoAsPrivileged
メソッドの呼出しに必要なjavax.security.auth.AuthPermission "doAsPrivileged"
権限を付与する点と、前述のFilePermissionを付与する次のプレースホルダーがある点が異なります:
grant CodeBase "file:./ReadFileAction.jar"
Principal javax.security.auth.kerberos.KerberosPrincipal
"your_user_name@your_realm" {
permission java.io.FilePermission "data/your_user_name_info.txt",
"read";
};
ここで、your_realm
は使用するKerberosレルムで、your_user_name@your_realm
とdata/your_user_name_info.txt
内のyour_user_name
は使用するユーザー名で置き換える必要があります。また、Windowsの場合、data/your_user_name_info.txt
内の「/」を「\」で置き換えてください。
サンプル・コードの実行
クライアントを装うサーバーのサンプル・コードを実行する場合、前のチュートリアルの「SampleClientおよびSampleServerプログラムの実行」に説明されているのと同じ操作を実行してください。ただし、次に示す点が異なります。
- 「
SampleServer
の実行準備」のステップSampleServer.java
のかわりにSampleServerImp.java
を使用します。次のコマンドを実行してコンパイルし、SampleServerImp.class
を含むSampleServerImp.jar
という名前のJARファイルを作成します。javac SampleServerImp.java jar -cvf SampleServerImp.jar SampleServerImp.class
- ポリシー・ファイル
server.policy
のかわりに、serverimp.policy
を使用します。 - ログイン構成ファイル
cs.conf
のかわりに、csImpLogin.conf
を使用します。 ReadFileAction.java
を、他のファイルと同じディレクトリにコピーします。次のコマンドを実行してコンパイルし、JARファイル内に格納します。javac ReadFileAction.java jar -cvf ReadFileAction.jar ReadFileAction.class
csImpLogin.conf
で、service_principal@your_realm
を、SampleServerを表すサービス・プリンシパルのKerberos名で置き換えます(Kerberosユーザー名およびサービス・プリンシパル名を参照)。serverimp.policy
内の2箇所に出現するservice_principal@your_realm
を、SampleServer
を表すサービス・プリンシパルのKerberos名で置き換えます。これは、ログイン構成ファイルで使用する名前と同じ名前です。また、your_realm
は使用するKerberosレルムで、your_user_name@your_realm
とdata/your_user_name_info.txt
内のyour_user_name
は使用するユーザー名で置き換える必要があります。Windowsで実行している場合、data/your_user_name_info.txt
内の「/」を「\」で置き換えてください。- 現在のディレクトリ内に
data
サブディレクトリを作成し、そのディレクトリ内に指定された名前の小さいテキスト・ファイルを作成します。たとえば、ユーザー名がmjones
の場合、dataサブディレクトリ内にmjones_info.txt
という名前のファイルを配置します。
- 「
SampleServer
の実行」ステップ-
このセクション内で指定されたコマンドの代わりに、次のコマンドを使用します。このコマンドにより、
SampleServerImp
の実行、serverimp.policy
およびcsImpLogin.conf
の使用、ReadFileAction.jar
の組込みを行うことができます。ノート:
重要: これらのコマンドの、
<port_number>
を適切なポート番号(4444などの大きなポート番号)に、<your_realm>
を使用するKerberosレルムに、<your_kdc>
を使用するKerberos KDCにそれぞれ置き換えてください。次に、Windowsのコマンドを示します。
java -classpath Login.jar;SampleServerImp.jar;ReadFileAction.jar -Djava.security.manager -Djava.security.krb5.realm=<your_realm> -Djava.security.krb5.kdc=<your_kdc> -Djava.security.policy=serverimp.policy -Djava.security.auth.login.config=csImpLogin.conf Login SampleServerImp <port_number>
次に、LinuxおよびmacOSのコマンドを示します。
java -classpath Login.jar:SampleServerImp.jar:ReadFileAction.jar -Djava.security.manager -Djava.security.krb5.realm=<your_realm> -Djava.security.krb5.kdc=<your_kdc> -Djava.security.policy=serverimp.policy -Djava.security.auth.login.config=csImpLogin.conf Login SampleServerImp <port_number>
通常どおり、コマンド全体は1行で入力してください。ここでは、読みやすくするために複数行に分けて表示してあります。システムに対しコマンドが長すぎる場合は、.batファイル(Windowsの場合)または.shファイル(LinuxおよびmacOSの場合)に記述し、そのファイルを実行して、コマンドを実行する必要がある場合があります。
SampleServer
の実行時に、SampleServerImp
の実行が予期されるサービス・プリンシパルのKerberosパスワードの入力が求められます。ログイン構成ファイルで指定されたKerberosログイン・モジュールにより、サービス・プリンシパルのKerberosへのログインが行われます。認証が成功すると、SampleServerImp
のコードがサービス・プリンシパルに代わって実行されます。このコードは、指定されたポート上でソケット接続を待機します。通常どおり、「
SampleClient
の実行準備」および「SampleClient
の実行」の指示に従って操作を実行し、ユーザー・ログインを行うと、クライアント・コードによりSampleServerImp
とのソケット接続が要求されます。SampleServerImp
が接続を受け付けると、SampleClient
およびSampleServerImp
により、前のチュートリアルで解説した方法で、共有コンテキストの確立およびメッセージの交換が行われます。メッセージの交換後に、
SampleServerImp
はクライアント・コードを実行するユーザーのプリンシパル名を判別して、その名前のプリンシパルを含むサブジェクトを新たに作成します。また、Subject.doAsPrivileged
を呼び出して、指定されたユーザーにかわってReadFileAction
内のコードを実行します。ReadFileAction
は、現行ディレクトリのdata
サブディレクトリ内のyour_user_name_info.txt
(your_user_name
は実際のユーザー名)という名前のファイルを読み取り、その内容を出力します。ログイン時のトラブルシューティングについては、「トラブルシューティング」を参照してください。
-
クライアントから委譲されたクレデンシャルの使用
クライアントがクレデンシャルをサーバーに委譲する場合、完成度のもっとも高い方法でクライアントを装うことができます。
コンテキスト・アクセプタ(前のチュートリアルのサーバー)とのコンテキストを確立する前に、コンテキスト・イニシエータ(クライアント)によりさまざまなコンテキスト・オプションの設定が行われたことを思い起こしてください。次に示すように、イニシエータがcontext
オブジェクトに対し、引数true
を指定してrequestCredDeleg
メソッドを呼び出す場合を考えましょう。
context.requestCredDeleg(true);
この場合、コンテキスト確立時に、イニシエータのクレデンシャルをアクセプタに委譲することが求められます。
イニシエータからアクセプタにクレデンシャルを委譲することにより、アクセプタが自らをイニシエータのエージェントまたは代理人として認証することが可能になります。
コンテキスト確立後に、アクセプタはクレデンシャルの委譲が実際に行われたかどうかを最初に確認する必要があります。これは、getCredDelegState
メソッドを呼び出すことで実行されます。
boolean delegated = context.getCredDelegState();
クレデンシャルが委譲されている場合、アクセプタはgetDelegCr
メソッドを呼び出して、そのクレデンシャルを取得できます。
GSSCredential clientCr = context.getDelegCred();
結果のGSSCredentialオブジェクトを使用して、以降のGSS-APIコンテキストをイニシエータの「委譲」として開始できます。たとえば、サーバーは、バックエンド・サーバーに対し、クライアントとして認証できます。バックエンド・サーバーには、中間的サーバーはどれかということよりも、元のクライアントがどれかということの方が重要です。
サーバーは、クライアントとして動作することにより、バックエンド・サーバーとの接続確立、結合セキュリティ・コンテキストの確立、およびメッセージ交換を、クライアントやサーバーと基本的に同じ方法で実行できます。
これを実行する1つの方法は、サーバーがGSSManagerのcreateContext
メソッドを呼び出す際に、createContext
にnull
ではなく委譲されたクレデンシャルを渡すことです。
別の選択肢として、サーバー・コードが最初にcom.sun.security.jgss.GSSUtil createSubject
メソッドを呼び出し、それに委譲されたクレデンシャルを渡すという方法もあります。このメソッドは、これらのクレデンシャルをデフォルトのクレデンシャルとして含むサブジェクトを返します。その後、「JAAS承認」チュートリアルのサブジェクトのアクセス制御コンテキストへの関連付けで説明したように、サーバーは、このSubject
を現行のAccessControlContextに関連付けることができます。次に、サーバー・コードは、GSSManagerのcreateContext
メソッドの呼出し時に、null (「現行の」サブジェクトをクレデンシャルとして使用することを指示する)を渡すことができます。違う言い方をすれば、サーバーが実質的にクライアントになります。GSSを使用するそのあとのバックエンド・サーバーへの接続は、前のチュートリアルで説明されるとおりの方法で確立できます。これは、委譲されたクレデンシャルを使用するコードが、デフォルトのローカル・クレデンシャルを使用するコードと同一であることが必要な場合に有用な方法です。
制約付き委任
制約付き委任がKDCサーバーで構成されている場合、サーバー側では、クライアントがrequestCredDeleg(true)を呼び出さない場合でも、KDC設定に応じて、getCredDelegState()呼出しは依然としてtrueを返す可能性があり、getDelegCred()は委譲されたクレデンシャルを返します。
クレデンシャルの委譲に必要なアクセス権
クレデンシャルを委譲するには、コンテキスト・イニシエータ(前のチュートリアルではSampleClient
)がjavax.security.auth.kerberos.DelegationPermission
を保持する必要があります。例を次に示します(斜体のプレースホルダーには実際の値を指定)。
permission javax.security.auth.kerberos.DelegationPermission
"\"service_principal@your_realm\"
\"krbtgt/your_realm@your_realm\"";
DelegationPermissionが保持する単一のターゲット内に、引用符で囲まれた2つの項目が含まれることに注目してください。内部の各引用符は、「\」でエスケープされています。このため、最初の項目は次のようになります。
"service_principal@your_realm"
2番目の項目は、次のようになります。
"krbtgt/your_realm@your_realm"
これは、基本的に、クライアントとして実行されるコードに対し、指定されたピアにKerberosチケットを転送するためのアクセス権を付与します(service_principal
)。Kerberosチケットは、krbtgt/your_realm@your_realm
からのサービス利用に使用します。
表示されているyour_realm
は、すべて実際のレルムで置き換えてください。また、service_principal@your_realm
も、サービス・プリンシパル名(サーバーを表すサービス・プリンシパルの名前)で置き換えてください。(前のチュートリアルの「Kerberosユーザーおよびサービス・プリンシパルの名前」を参照)。レルムがKRBNT-OPERATIONS.EXAMPLE.COM
で、サービス・プリンシパルがsample/raven.example.com@KRBNT-OPERATIONS.EXAMPLE.COM
である場合を考えてみましょう。この場合、アクセス権をポリシー・ファイル内で次のように表示できます。
permission javax.security.auth.kerberos.DelegationPermission
"\"sample/raven.example.com@KRBNT-OPERATIONS.EXAMPLE.COM\"
\"krbtgt/KRBNT-OPERATIONS.EXAMPLE.COM@KRBNT-OPERATIONS.EXAMPLE.COM\"";