Java Security Managerを有効にしてSpring Bootを起動する

Spring BootをJavaのSecurity Managerと一緒に起動することで、お手軽にサンドボックス化できます。例えば外部から送られてくるスクリプトをアプリケーション上で実行しなきゃいけないけど、セキュリティが不安な時とか。

**2021/05/17 2:07 JST: 実際に試してみたら工夫が必要だったので説明を加えました。** ~~見切り発車で書いてました。ごめんなさい。~~

# Java Security Manager
JavaのSecurity Managerという機能を有効にすると、プログラムが実行してもよい操作を制限することができます。例えばファイルや環境変数の読み書き、ネットワークへの接続など、想定外の挙動をした場合にそれを検知して、失敗させることができます。
許可する権限はポリシーファイルというものに書き連ねます。

```example.policy
grant {
    // 全て許可する
    permission java.security.AllPermission;
};
```

そして、Javaを実行する際に```-Djava.security.manager```と```-Djava.security.policy```オプションも付けます。```-Djava.security.manager```だけの場合は、デフォルトのポリシーファイルが使用されます。

```bash
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側のコンパイルが通ります。

```build.gradle
dependencies {
    ...
    compileOnly files('libs/myutils.jar')
}

```

ポリシーはこんな感じになります。

```example.policy
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/```を設定して起動します。

```bash
java -cp my_application.jar -Dloader.path=libs/ org.springframework.boot.loader.PropertiesLauncher
```

Security Managerのオプションも付ければ完成！

```bash
java -cp my_application.jar -Djava.security.manager -Djava.security.policy=example.policy -Dloader.path=libs/ org.springframework.boot.loader.PropertiesLauncher
```

## 実行
これで良さそうなんですが、JDK 15でやるとなんかエラーが出ます。

```bash
$ 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```オプションも付けると直ります。

```bash
$ 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