鉴于 Tomcat 的 Context XML 文件往往包含敏感信息(通常包括连接到数据库所需的凭据),我如何才能从纯文本 context.xml 以外的源动态加载这些值?
4 回答
假设您有一个看起来像这样的 tomcat/conf/context.xml 文件:
<?xml version="1.0" encoding="utf-8"?>
<Context>
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<Resource
name="jdbc/MyDB"
auth="Container"
type="javax.sql.DataSource"
removeAbandoned="true"
removeAbandonedTimeout="15"
maxActive="5"
maxIdle="5"
maxWait="7000"
username="${db.mydb.uid}"
password="${db.mydb.pwd}"
driverClassName="${db.mydb.driver}"
url="${db.mydb.url}${db.mydb.dbName}?autoReconnectForPools=true&characterEncoding=UTF-8"
factory="com.mycompany.util.configuration.CustomDataSourceFactory"
validationQuery="SELECT '1';"
testOnBorrow="true"/>
</Context>
在这种情况下,我们要替换的是此资源定义中 ${.*} 内容中的任何内容。但是,只要对下面的代码稍作修改,您就可以根据您想要的任何标准执行这些替换。
注意线factory="com.mycompany.util.configuration.CustomDataSourceFactory"
这意味着 Tomcat 将尝试使用这个工厂来处理这个资源。应该提到的是,这意味着这个工厂必须在启动时位于 Tomcat 的类路径中(我个人将我的工厂放在 Tomcatlib
目录中的 JAR 中)。
这是我的工厂的样子:
package com.mycompany.util.configuration;
import java.util.Hashtable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.naming.spi.ObjectFactory;
import org.apache.commons.dbcp.BasicDataSourceFactory;
public class CustomDataSourceFactory extends BasicDataSourceFactory implements ObjectFactory {
private static final Pattern _propRefPattern = Pattern.compile("\\$\\{.*?\\}");
//http://tomcat.apache.org/tomcat-6.0-doc/jndi-resources-howto.html#Adding_Custom_Resource_Factories
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment) throws Exception {
if (obj instanceof Reference) {
Reference ref = (Reference) obj;
System.out.println("Resolving context reference values dynamically");
for(int i = 0; i < ref.size(); i++) {
RefAddr addr = ref.get(i);
String tag = addr.getType();
String value = (String) addr.getContent();
Matcher matcher = _propRefPattern.matcher(value);
if (matcher.find()) {
String resolvedValue = resolve(value);
System.out.println("Resolved " + value + " to " + resolvedValue);
ref.remove(i);
ref.add(i, new StringRefAddr(tag, resolvedValue));
}
}
}
// Return the customized instance
return super.getObjectInstance(obj, name, nameCtx, environment);
}
private String resolve(String value) {
//Given the placeholder, do stuff to figure out what it's true value should be, and return that String.
//This could be decryption, or maybe using a properties file.
}
}
然后,一旦此代码位于类路径中,重新启动 Tomcat 并观察 catalina.out 中的日志消息。注意:这些System.out.println
语句最终可能会将敏感信息打印到您的日志中,因此您可能希望在完成调试后将其删除。
在旁注中,我写了这个,因为我发现许多示例过于特定于一个特定主题(例如使用密码学),我想展示如何通用地完成此操作。此外,这个问题的其他一些答案并不能很好地解释自己,我不得不做一些挖掘来弄清楚需要做什么才能完成这项工作。我想和你们分享我的发现。如果您发现问题,请随时对此发表评论,提出任何问题或进行更正,我一定会将修复程序纳入我的答案。
为什么要经历所有的麻烦?
只需将主机上的敏感参数配置为JVM 系统属性,Tomcat 就会自动识别它们并替换所有占位符的值${...}
。这样,敏感数据只保留在主机上,永远不会在源代码中泄露。
来自:https ://tomcat.apache.org/tomcat-7.0-doc/config/
支持 Apache Ant 风格的变量替换;名称为 propname 的系统属性可以使用语法 ${propname} 在配置文件中使用。所有系统属性都可用,包括使用 -D 语法设置的属性、JVM 自动提供的属性以及在 $CATALINA_BASE/conf/catalina.properties 文件中配置的属性。
@ 注意:这个答案假定您context.xml
和其他 Tomcat 配置文件在 SCM 下,这通常在使用虚拟化部署(例如 Openshift)时发生。
如果你想这样做,你可以实现你自己的实现非常简单的类
org.apache.tomcat.util.IntrospectionUtils.PropertySource
接口并使用系统属性注册它
org.apache.tomcat.util.digester.PROPERTY_SOURCE
我们为能够在 context.xml、server.xml 等中使用加密值做了同样的事情。
请参阅http://tomcat.apache.org/tomcat-7.0-doc/config/systemprops.html#Property_replacements
如果您正在实施 zookeeper 属性源,我对结果感兴趣,因为我正在考虑这样做。
你不能。这个问题意味着无限的倒退。如果您有一个安全源来加载凭据,那么您将需要为安全源定义凭据,依此类推 +ad infinitum.+
最终,这类事情的答案是服务器机器的物理安全,以及对谁可以从外部查看其部署目录的访问控制。