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来实现自定义;
posted @ 2021-03-25 22:20  无风听海  阅读(653)  评论(0编辑  收藏  举报