seraphyの日記

日記というよりは過去を振り返るときのための単なる備忘録

Java7u51以降でApache Derbyのネットワークサーバを使う場合の設定

概要

最近アップデートされたJava7u51を使用したところ、
Apache Derby 10.10のネットワークサーバを起動しようとすると、以下のような例外が発生して起動できなくなっていた。

db-derby-10.10.1.1-bin\bin>startNetworkServer
Fri Feb 14 13:15:27 JST 2014 : セキュリティーマネージャーが Basic サーバーセキュ
リティーポリシーを使用してインストールされました。
Fri Feb 14 13:15:27 JST 2014 : access denied ("java.net.SocketPermission" "localhost:1527" "listen,resolve")
...

access denied ("java.net.SocketPermission" "localhost:1527" "listen,resolve")


どうやらセキュリティ例外らしいが、これは何が原因で、どうすれば良いのか調べてみた。

原因と対策

結論的にいうと、これはJava7u51のデフォルトのセキュリティポリシーの変更が原因である。

Java7u51のデフォルトのセキュリティポリシーの変更点

Java7u51によるソケット権限に関する、デフォルトのセキュリティポリシーの変更についてはリリースノートに説明がある。

Java SE Development Kit 7, Update 51 (JDK 7u51)のリリースノートより抜粋

Change in Default Socket Permissions

The default socket permissions assigned to all code including untrusted code have been changed in this release.

Previously, all code was able to bind any socket type to any port number greater than or equal to 1024.

It is still possible to bind sockets to the ephemeral port range on each system.

The exact range of ephemeral ports varies from one operating system to another, but it is typically in the high range (such as from 49152 to 65535).

The new restriction is that binding sockets outside of the ephemeral range now requires an explicit permission in the system security policy.


Most applications using client tcp sockets and a security manager will not see any problem, as these typically bind to ephemeral ports anyway.

Applications using datagram sockets or server tcp sockets (and a security manager) may encounter security exceptions where none were seen before.

If this occurs, users should review whether the port number being requested is expected, and if this is the case, a socket permission grant can be added to the local security policy, to resolve the issue.

これによると、セキュリティマネージャが有効になっている場合のデフォルトのポリシーとして、信頼されていない(=有効な署名がない)アプリケーションでは、エフェメラルポートを除いて、明示的に許可を与えないかぎりポートをバインドすることができない、という制約がつけられるようになったようである。


従来はアプリケーションが信頼されているかを問わず1024以上の、どんなポートもバイント可能だった。


このリリースノートでは、特定のプロトコルでの利用が決められていない一時的な通信のために自由に使うポート(エフェメラルポート)は、個々のOSによるとして具体的に示していないが、一般的には49152から65535の数値の大きなポート範囲で運用することを想定する旨が示されている。


この範囲は、IANAでの提言による"DYNAMIC AND/OR PRIVATE PORTS"(動的・私用ポート)の範囲(49152..65535)に合わせているようである。


OSごとのエフェメラルポートは以下のようになっている。
http://www.wdic.org/w/WDIC/%E3%82%A8%E3%83%95%E3%82%A7%E3%83%A1%E3%83%A9%E3%83%AB%E3%83%9D%E3%83%BC%E3%83%88

  • Linuxではnet.ipv4.ip_local_port_range というカーネルパラメータが示す範囲
    • Kernel2.4以降、CentOSなどRHEL互換だと既定では32768から61000の範囲
    • Kernel2.2時代の古いものは1024から4999の範囲だった。
  • Windows Vista以降ではIANAと同じ (http://support.microsoft.com/kb/929851)
    • XP以前だと既定では1025から5000の範囲だった。
  • BSD系もIANAと同じなので、おそらくMac OS Xも同じではないかと思われる。(OSXはIANAと同じという記述はネット上にたくさんあるが、公式な資料がみつからなかった。)


上記から、最大公約数的には、このIANAで定義されている数値49152以上で、且つ、Linuxの既定の上限である61000までの範囲を使っておけば安全そうではある。


Apache Derbyに付属するネットワークサーバでは既定で1527ポートを使用するために、上記エフェメラルポートの範囲外となり、

  1. アプリケーションが署名されていない
  2. 明示的にセキュリティポリシーでポートを許可していない

上記の条件に合致するするため、セキュリティ例外が発生することになる。

対策

この対策についても、このリリースノートにちゃんと書いてある。

Area: other-libs/javadb

Synopsis: Additional permission needed to run Java DB network server


An additional permission may be needed in order to bring up the Java DB network server.

In particular, the startup scripts in may fail to boot the network server.

This is a result of the "Better applet networking" changes made by 8011787 (not public).

While attempting to boot, the network server may fail and raise the following error:

access denied ("java.net.SocketPermission" "localhost:1527" "listen,resolve")
java.security.AccessControlException: access denied 
("java.net.SocketPermission" "localhost:1527" "listen,resolve")

To fix this problem, you must bring up the network server with a security policy which includes the missing permission.

Instead of booting the network server as:

java org.apache.derby.drda.NetworkServerControl start

boot the network server as follows:

java -Djava.security.manager -Djava.security.policy=${yourPolicyFile}
org.apache.derby.drda.NetworkServerControl start

where ${yourPolicyFile} is a file containing a customized version of the policy file described in the Java DB Admin Guide section titled Basic Network Server security policy.

You must customize that generic policy file to fit your application.

In addition, you must add the following permission to the permissions block granted to the ${derby.install.url}derbynet.jar codebase:

permission java.net.SocketPermission "localhost:${port}", "listen";

where ${port} should be replaced by the port number where the network server listens for incoming connection requests.

By default, that is port 1527.

For more information on Java DB security policies, see the Java DB Admin Guide sections titled Network Server security and Running the Network Server under the security manager.

If you are using replication, a similar permission must be granted to the security policy for the slave server.

Add the following permission to the ${derby.install.url}derby.jar codebase:

permission java.net.SocketPermission "localhost:${slavePort}", "listen";

where ${slavePort} should be replaced by the port number where the slave server listens for incoming connection requests (typically 4851).

For more information on the security policy for the slave server, see the Java DB Admin Guide section titled Replication and security.

要約すると、ポリシーファイルをつくって1527ポートを許可しろ、ってことである。


ググってみると、この対策の具体的な手順も、先達の方々が、すでに公開されていた。


もし、既存のポリシーを生かしたままSocketPermissionを加えたいのであれば、Derby自身が必要とするポリシーなどについては、公式ドキュメントに記載があるので、これらとあわせて設定すれば良いようである。

http://db.apache.org/derby/docs/10.10/devguide/cdevcbabejdfj.html



...しかし、全権許可するだけにPolicyを設定するぐらいなら、セキュリティマネージャを外したほうがいいんじゃないか、と思う。

パーミッションを変更する必要のない、もっと簡単な方法(開発環境向け)

だれがセキュリティマネージャを設定しているのか?

そもそも、セキュリティポリシーが効いているということは、セキュリティマネージャが明示的に設定されている、ということである。

セキュリティマネージャはJavaの起動時にシステムプロパティで指定するか、プログラム内部から明示的にセキュリティマネージャを設定する必要がある。

ということは、Apache Derbyのネットワークサーバは、いずれかの方法によってセキュリティマネージャを有効にしているはずである。


これを設定しなければセキュリティポリシーは適用されないので、この問題も解決するし、ポリシーファイルを用意するとかめんどくさいことをしなくても良い。


しかし、バッチファイルを流し読んでみたが、セキュリティマネージャを指定するようなシステムプロパティは見つからない。

ということは、プログラム内部から有効にしているはずである。


Apache Derby 10.10はオープンソースなのでソースを見ることができる。


予想通りである。

しかも、セキュリティマネージャを有効にしないための仕組みも用意されていた。


NetworkServerControl.javaの中で以下のようなコードがあった。

    /**
     * Return true if we need to install a Security Manager. All of the
     * following must apply. See DERBY-2196.
     *
     * <ul>
     * <li>The VM was booted with NetworkServerContro.main() as the
     * entry point. This is handled by the fact that this method is only called
     * by main().</li>
     * <li>The VM isn't already running a SecurityManager.</li>
     * <li>The command must be "start".</li>
     * <li>The customer didn't specify the -noSecurityManager flag on the startup command
     * line.</li>
     * </ul>
     */
    private static  boolean needsSecurityManager( NetworkServerControlImpl server, int command )
        throws Exception
    {
        return
            (
             (System.getSecurityManager() == null) &&
             (command == NetworkServerControlImpl.COMMAND_START) &&
             (!server.runningUnsecure())
             );
   }

ソースを読むと、コマンドラインの引数で"-noSecurityManager"と指定されている場合はセキュリティマネージャは設定されないことになっている。


このオプションについてググッてみても公式ドキュメントの記述にはたどり着かなかったのだが、かわりにエラーメッセージに使われていると思われるリソースファイルは見つかった。

ネットワークサーバーの起動に失敗しました。ネットワークサーバーでセキュリティーマネージャーをインストールする前に、ユーザー認証を有効にする必要があります。ユーザー認証を有効にするか、セキュリティーマネージャーのインストールを無効にしてください。 ユーザー認証を有効にする手順については、『Derby Developer''s Guide』の「Working with user authentication」のセクションを参照してください。クライアント/サーバー環境でセキュリティーマネージャーのインストールを無効にすることは、できるかぎり避けてください。どうしても無効にする必要がある場合は、ネットワークサーバーの起動時にコマンド行オプション "-noSecurityManager" を指定して、セキュリティーマネージャーのインストールを無効にすることができます。


興味深いアドバイスが見て取れるが、とりあえず一応、Derbyのネットワークサーバは、セキュリティを外しても動く仕組みはもっているようである。

ネットワークサーバの起動バッチの書き換え

Windowsの場合であれば、startNetworkServer.batを書き換えて、

CALL "%~dp0derby_common.bat" %*

で各種環境変数を設定したあとで、以下の1行を加えれば良い。

set DERBY_CMD_LINE_ARGS=-noSecurityManager %DERBY_CMD_LINE_ARGS%

こうすると、

db-derby-10.10.1.1-bin\bin>startNetworkServer
Fri Feb 14 15:30:26 JST 2014 : Apache Derby Network Server - 10.10.1.1 - (145826
8)が起動し、ポート1527で接続の受入れ準備が完了しました

と、うまく起動できるようになっていることが確認できる。


ちなみに、デフォルトのDerbyのネットワークサーバーのポリシーは、すべてのファイルへの読み書き削除が許可されているので、他のマシンからdbにアクセスすることで、どこにでもDBを作成できてしまったりするので、その点についてはファイアウォールで守らないかぎり(ポリシーがあってもなくても)ザルである。

結論

Derbyが自分の使うポートについてSocketPermissionを設定するのが最適解だと思うし、いずれ、そのように修正されるものと思われるが、

さしあたり、自分のマシンと信頼できる仲間の間の開発用途では「-noSecurityManager」でセキュリティマネージャを外すのも手だと思う。


※ なお、Apache Derbyの埋め込みモードの場合はDerbyは単なるライブラリとなるので、アプリケーション自身のセキュリティ設定に従う。アプリがセキュリティマネージャを設定していなければDerbyもセキュリティを外した状態となるし、仮にセキュリティマネージャを使用していても、埋め込みモードではソケット通信するわけでもないので埋め込みモードのDerbyでは本問題は何の関係もない。