天天看點

elasticsearch之自定義Java代碼的安全政策管理

提出問題

在我們首次使用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
           

總結

  1. 預設情況下不會啟用代碼權限控制;要啟用權限控制需要在啟動入口使用代碼顯示的啟用;
  2. 代碼權限是針對某些代碼授予一些操作的權限,這些操作都是需要通過調用JVM的,JVM負責進行權限校驗;
  3. Java的代碼權限安全架構,提供了不同測試的自定義靈活性,我們既可以通過安全政策檔案進行自定義,也可以通過實作自己的Policy或者SecurityManage來實作自定義;

繼續閱讀