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
总结
- 默认情况下不会启用代码权限控制;要启用权限控制需要在启动入口使用代码显示的启用;
- 代码权限是针对某些代码授予一些操作的权限,这些操作都是需要通过调用JVM的,JVM负责进行权限校验;
- Java的代码权限安全框架,提供了不同测试的自定义灵活性,我们既可以通过安全策略文件进行自定义,也可以通过实现自己的Policy或者SecurityManage来实现自定义;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现