【发布时间】:2009-05-28 19:36:12
【问题描述】:
我们有一些应用程序有时会进入不良状态,但仅限于生产环境(当然!)。虽然进行堆转储有助于收集状态信息,但使用远程调试器通常更容易。设置它很容易——只需将它添加到他的命令行中:
-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=PORT
似乎没有可用的安全机制,因此在生产中打开调试将有效地允许任意代码执行(通过热插拔)。
我们混合了 1.4.2 和 1.5 Sun JVM,在 Solaris 9 和 Linux (Redhat Enterprise 4) 上运行。我们如何启用安全调试?还有其他方法可以实现我们的生产服务器检查目标吗?
更新: 对于 JDK 1.5+ JVM,可以指定调试器应该绑定的接口和端口。因此,KarlP 建议绑定到环回并仅使用 SSH 隧道连接到本地开发人员框应该可以在服务器上正确设置 SSH 的情况下工作。
但是,JDK1.4x 似乎不允许为调试端口指定接口。那么,我们可以阻止对网络中某处的调试端口的访问,或者在操作系统本身中进行一些特定于系统的阻止(Jared 建议的 IPChains 等)?
更新 #2:这是一个可以让我们限制风险的 hack,即使在 1.4.2 JVM 上也是如此:
命令行参数:
-Xdebug
-Xrunjdwp:
transport=dt_socket,
server=y,
suspend=n,
address=9001,
onthrow=com.whatever.TurnOnDebuggerException,
launch=nothing
开启调试器的Java代码:
try {
throw new TurnOnDebuggerException();
} catch (TurnOnDebugger td) {
//Nothing
}
TurnOnDebuggerException 可以是保证不会在其他任何地方抛出的任何异常。
我在 Windows 机器上对此进行了测试,以证明 (1) 调试器端口最初不接收连接,以及 (2) 如上所示抛出 TurnOnDebugger 异常会导致调试器激活。启动参数是必需的(至少在 JDK1.4.2 上),但是 JVM 可以优雅地处理垃圾值。
我们正计划制作一个小型 servlet,在适当的安全性之后,它可以让我们打开调试器。当然,之后不能关闭它,并且调试器一旦打开它仍然会乱听。但是,这些是我们愿意接受的限制,因为生产系统的调试总是会导致之后重新启动。
更新 #3: 我最终编写了三个类:(1) TurnOnDebuggerException,一个普通的 Java 异常,(2) DebuggerPoller,一个后台线程,用于检查指定文件是否存在在文件系统上,以及 (3) DebuggerMainWrapper,一个启动轮询线程然后反射性地调用另一个指定类的 main 方法的类。
这是它的使用方式:
- 在启动脚本中用 DebuggerMainWrapper 替换“主”类
- 添加两个系统 (-D) 参数,一个指定真正的主类,另一个指定文件系统上的文件。
- 在命令行上配置调试器,添加 onthrow=com.whatever.TurnOnDebuggerException 部分
- 将包含上述三个类的 jar 添加到类路径中。
现在,当您启动 JVM 时,除了启动了一个后台轮询线程之外,一切都相同。假设该文件(我们的称为 TurnOnDebugger)最初不存在,轮询器每 N 秒检查一次。当轮询器第一次注意到它时,它会抛出并立即捕获 TurnOnDebuggerException。然后,代理被启动。
您无法将其重新关闭,而且机器在打开时也不是很安全。从好的方面来说,我不认为调试器允许多个同时连接,所以保持调试连接是你最好的防御。我们选择文件通知方法是因为它允许我们通过在只有正确使用权限的目录中指定触发器文件来搭载现有的 Unix authen/author。您可以轻松地构建一个通过套接字连接实现相同目的的小 war 文件。当然,由于我们无法关闭调试器,我们只会在关闭有病的应用程序之前使用它来收集数据。如果有人想要这个代码,请告诉我。但是,您只需几分钟就可以自己组装起来。
【问题讨论】: