提出問題
在我們首次使用intellij直接運作elasticsearch的源代碼的時候,我們必然會碰到下邊這樣一個安全性的問題(異常堆棧隻截取了一部分),如果在深夜中靜下心來思考一下,為什麼直接執行發行包不發生這個錯誤呢?
Caused by: java.security.AccessControlException: access denied ("java.lang.RuntimePermission" "createClassLoader")
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:472) ~[?:?]
at java.security.AccessController.checkPermission(AccessController.java:1044) ~[?:?]
at java.lang.SecurityManager.checkPermission(SecurityManager.java:408) ~[?:?]
at java.lang.SecurityManager.checkCreateClassLoader(SecurityManager.java:470) ~[?:?]
at java.lang.ClassLoader.checkCreateClassLoader(ClassLoader.java:369) ~[?:?]
at java.lang.ClassLoader.checkCreateClassLoader(ClassLoader.java:359) ~[?:?]
at java.lang.ClassLoader.<init>(ClassLoader.java:456) ~[?:?]
at org.elasticsearch.plugins.ExtendedPluginsClassLoader.<init>(ExtendedPluginsClassLoader.java:36) ~[main/:?]
at org.elasticsearch.plugins.ExtendedPluginsClassLoader.lambda$create$0(ExtendedPluginsClassLoader.java:57) ~[main/:?]
at java.security.AccessController.doPrivileged(AccessController.java:310) ~[?:?]
at org.elasticsearch.plugins.ExtendedPluginsClassLoader.create(ExtendedPluginsClassLoader.java:56) ~[main/:?]
at org.elasticsearch.plugins.PluginLoaderIndirection.createLoader(PluginLoaderIndirection.java:31) ~[main/:?]
at org.elasticsearch.plugins.PluginsService.loadBundle(PluginsService.java:545) ~[main/:?]
at org.elasticsearch.plugins.PluginsService.loadBundles(PluginsService.java:471) ~[main/:?]
at org.elasticsearch.plugins.PluginsService.<init>(PluginsService.java:163) ~[main/:?]
at org.elasticsearch.node.Node.<init>(Node.java:339) ~[main/:?]
at org.elasticsearch.node.Node.<init>(Node.java:266) ~[main/:?]
at org.elasticsearch.bootstrap.Bootstrap$5.<init>(Bootstrap.java:212) ~[main/:?]
at org.elasticsearch.bootstrap.Bootstrap.setup(Bootstrap.java:212) ~[main/:?]
at org.elasticsearch.bootstrap.Bootstrap.init(Bootstrap.java:333) ~[main/:?]
at org.elasticsearch.bootstrap.Elasticsearch.init(Elasticsearch.java:159) ~[main/:?]
探索問題根源
通過上邊的異常堆棧資訊,我們可以看到是執行ExtendedPluginsClassLoader的create方法時候抛出了異常,如果仔細的分析一下,可以看到在create方法裡邊調用了ExtendedPluginsClassLoader的構造函數,緊接着調用了ClassLoader的構造方法,這裡進行checkCreateClassLoader的時候沒有通過。接下來看下源代碼,可以看到這是一個相對比較簡單的類,但是它繼承了Java内置的ClassLoader類。
public class ExtendedPluginsClassLoader extends ClassLoader {
private final List<ClassLoader> extendedLoaders;
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
for (ClassLoader loader : extendedLoaders) {
try {
return loader.loadClass(name);
} catch (ClassNotFoundException e) {
// continue
}
}
throw new ClassNotFoundException(name);
}
public static ExtendedPluginsClassLoader create(ClassLoader parent, List<ClassLoader> extendedLoaders) {
return AccessController.doPrivileged((PrivilegedAction<ExtendedPluginsClassLoader>)
() -> new ExtendedPluginsClassLoader(parent, extendedLoaders));
}
}
Java提供了一整套的安全機制,這裡涉及到的是SecurityManager,負責管理類的具體的操作權限,例如這裡的createClassLoader權限。但是預設情況下是不啟用SecurityManager的,這裡報錯了,那elasticsearch肯定是啟用了,我們來看下啟動的源代碼,在ElasticSearch的main方法中确實是啟用了SecurityManager,從注釋上可以看到已經授予全部的權限,現在報錯了可能在後邊執行過程中重置了SecurityManager,我們接着往下看
public static void main(final String[] args) throws Exception {
overrideDnsCachePolicyProperties();
System.setSecurityManager(new SecurityManager() {
@Override
public void checkPermission(Permission perm) {
// grant all permissions so that we can later set the security manager to the one that we want
}
});
LogConfigurator.registerErrorListener();
final Elasticsearch elasticsearch = new Elasticsearch();
int status = main(args, elasticsearch, Terminal.DEFAULT);
if (status != ExitCodes.OK) {
exit(status);
}
}
通過查找發現在Bootstrap的setup方法中對安全進行了配置
// install SM after natives, shutdown hooks, etc.
try {
Security.configure(environment, BootstrapSettings.SECURITY_FILTER_BAD_DEFAULTS_SETTING.get(settings));
} catch (IOException | NoSuchAlgorithmException e) {
throw new BootstrapException(e);
}
可以看到,在Security的configure方法直接中對Policy進行了重置,通過對createPermissions和getPluginPermissions進行分析,未發現有針對ExtendedPluginsClassLoader進行授權的處理或者配置。
static void configure(Environment environment, boolean filterBadDefaults) throws IOException, NoSuchAlgorithmException {
// enable security policy: union of template and environment-based paths, and possibly plugin permissions
Map<String, URL> codebases = getCodebaseJarMap(JarHell.parseClassPath());
Policy.setPolicy(new ESPolicy(codebases, createPermissions(environment), getPluginPermissions(environment), filterBadDefaults));
}
我們來看ESPolicy的初始化邏輯,經過分析隻有第一行針對特定的codebases進行授權配置,這裡讀取的權限配置檔案是server\src\main\resources\org\elasticsearch\bootstrap\security.policy,我們來看下這個檔案
ESPolicy(Map<String, URL> codebases, PermissionCollection dynamic, Map<String,Policy> plugins, boolean filterBadDefaults) {
this.template = Security.readPolicy(getClass().getResource(POLICY_RESOURCE), codebases);
this.untrusted = Security.readPolicy(getClass().getResource(UNTRUSTED_RESOURCE), Collections.emptyMap());
if (filterBadDefaults) {
this.system = new SystemPolicy(Policy.getPolicy());
} else {
this.system = Policy.getPolicy();
}
this.dynamic = dynamic;
this.plugins = plugins;
}
在檔案中隻有下邊這一個是控制createClassLoader權限的,通過project的結構視圖,plugin-classloader就是ExtendedPluginsClassLoader所在的項目,直接執行發行包沒有問題,那這裡應該是針對jar包進行的授權。
grant codeBase "${codebase.plugin-classloader}" {
// needed to create the classloader which allows plugins to extend other plugins
permission java.lang.RuntimePermission "createClassLoader";
};
通過JarHell的parseClassPath可以看到codebases最終來源于"java.class.path"
public static Set<URL> parseClassPath() {
return parseClassPath(System.getProperty("java.class.path"));
}
通過分析es的啟動腳本,啟動傳遞的是es根目錄下的lib,裡邊都是jar檔案,印證了我們前邊的猜測。
# now set the classpath
ES_CLASSPATH="$ES_HOME/lib/*"
當我們在Intellij中執行es的時候,Intellij自動傳遞的是它自動生成的class檔案所在的目錄
-classpath F:\source\elasticsearch-6.8.12\server\build\classes\java\main;F:\source\elasticsearch-6.8.12\server\out\production\resources;F:\source\elasticsearch-6.8.12\libs\x-content\build\classes\java\main;
總結一下問題的根源,由于es自定義了java的代碼安全政策,其在自己的security.policy檔案中對createClassLoader權限進行了限制,隻授權給了plugin-classloader,由于使用Intellij直接使用自己生成的class檔案執行es,是以才會出現權限問題。
解決方案
最簡單的方式就是建立一個專用的授權檔案security_dev.policy,在裡邊進行授權
grant codeBase "file:/F:/source/elasticsearch-6.8.12/libs/plugin-classloader/build/classes/java/main/" {
permission java.lang.RuntimePermission "createClassLoader";
};
并将自定義的檔案路徑添加到JVM的啟動參數中即可
-Djava.security.policy=F:\source\elasticsearch-6.8.12\server\src\main\resources\org\elasticsearch\bootstrap\security_dev.policy
總結
- 預設情況下不會啟用代碼權限控制;要啟用權限控制需要在啟動入口使用代碼顯示的啟用;
- 代碼權限是針對某些代碼授予一些操作的權限,這些操作都是需要通過調用JVM的,JVM負責進行權限校驗;
- Java的代碼權限安全架構,提供了不同測試的自定義靈活性,我們既可以通過安全政策檔案進行自定義,也可以通過實作自己的Policy或者SecurityManage來實作自定義;