Spring BootをJavaのSecurity Managerと一緒に起動することで、お手軽にサンドボックス化できます。例えば外部から送られてくるスクリプトをアプリケーション上で実行しなきゃいけないけど、セキュリティが不安な時とか。
2021/05/17 2:07 JST: 実際に試してみたら工夫が必要だったので説明を加えました。 見切り発車で書いてました。ごめんなさい。
Java Security Manager
JavaのSecurity Managerという機能を有効にすると、プログラムが実行してもよい操作を制限することができます。例えばファイルや環境変数の読み書き、ネットワークへの接続など、想定外の挙動をした場合にそれを検知して、失敗させることができます。
許可する権限はポリシーファイルというものに書き連ねます。
grant {
// 全て許可する
permission java.security.AllPermission;
};
そして、Javaを実行する際に-Djava.security.manager
と-Djava.security.policy
オプションも付けます。-Djava.security.manager
だけの場合は、デフォルトのポリシーファイルが使用されます。
java -Djava.security.manager -Djava.security.policy=example.policy
Spring BootでSecurity Managerを有効にする
Spring Bootと一緒にSecurity Managerを使おうとするとすこし工夫が必要になります。
準備
ポリシーはパッケージや個別クラスごとに設定ができず、jarやjarのあるディレクトリごとにしかできません。従って、Spring Bootの入ってるjarにはすべてを許可し(そうしないとTomcatとか諸々起動しない)、制限したいプログラムは外部のjarにまとめる必要があります。
イメージとしてこんな感じ。
./
├─ libs/ <== 制限したいプログラムをいれる場所
│ └─ myutils.jar
├─ example.policy
└─ my_application.jar <== Spring Bootの実行可能jar
Gradleを使っている人はcompileOnly files
で外部jarを指定すれば、Spring Boot側のコンパイルが通ります。
dependencies {
...
compileOnly files('libs/myutils.jar')
}
ポリシーはこんな感じになります。
grant codeBase "file:my_application.jar" {
// Spring Bootアプリケーションには全て許可する
permission java.security.AllPermission;
};
grant codeBase "file:libs/*" {
// 例: libs内のjarには、my_test.txtの読み書きだけ許可する
permission java.io.FilePermission "C:/my_test.txt", "read,write";
};
Spring Bootを実行する際に追加のクラスパスを指定する方法として、PropertiesLauncherが使えます。
以下のコマンドのように、Spring Bootを実行する際に-Dloader.path
で追加のクラスパスlibs/
を設定して起動します。
java -cp my_application.jar -Dloader.path=libs/ org.springframework.boot.loader.PropertiesLauncher
Security Managerのオプションも付ければ完成!
java -cp my_application.jar -Djava.security.manager -Djava.security.policy=example.policy -Dloader.path=libs/ org.springframework.boot.loader.PropertiesLauncher
実行
これで良さそうなんですが、JDK 15でやるとなんかエラーが出ます。
$ java -version
java version "15.0.1" 2020-10-20
Java(TM) SE Runtime Environment (build 15.0.1+9-18)
Java HotSpot(TM) 64-Bit Server VM (build 15.0.1+9-18, mixed mode, sharing)
$ java -cp my_application.jar -Djava.security.manager -Djava.security.policy=example.policy -Dloader.path=libs/ org.springframework.boot.loader.PropertiesLauncher
Exception in thread "main" java.lang.ClassNotFoundException: com.github.otoiku.Application
at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:435)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:589)
at org.springframework.boot.loader.LaunchedURLClassLoader.loadClass(LaunchedURLClassLoader.java:151)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:468)
at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:46)
at org.springframework.boot.loader.Launcher.launch(Launcher.java:107)
at org.springframework.boot.loader.Launcher.launch(Launcher.java:58)
at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:88)
JDK 11以上の場合
結論から言えば、-Dsun.misc.URLClassPath.disableJarChecking
オプションも付けると直ります。
$ java -cp my_application.jar -Djava.security.manager -Dsun.misc.URLClassPath.disableJarChecking -Djava.security.policy=example.policy -Dloader.path=libs/ org.springframework.boot.loader.PropertiesLauncher
なにやらJDK 11時代からあるSpring Bootのバグらしく、GitHubに報告されてから2年近くたった今も直っていません(2021年5月現在)。
https://stackoverflow.com/questions/54063602/springboot-on-open-jdk-11-classnotfound-errors-when-securitymanager-is-activ
https://github.com/spring-projects/spring-boot/issues/17796
https://github.com/spring-projects/spring-boot/issues/25538