Skip to content
luanjia edited this page Mar 5, 2019 · 3 revisions

BANNER

配置沙箱

JVM-Sandbox是即插即用的,本身并不需要特别的配置。为了更好地使用JVM-Sandbox和管理模块,配置上做了一些约定和规范。—— “道路千万条,安全第一条,使用不规范,亲人两行泪”

沙箱结构介绍

应用目录结构

./sandbox/
    +--bin/
    |   +--sandbox.sh
    |
    +--cfg/
    |   +--sandbox-logback.xml
    |   +--sandbox.properties
    |   `--version
    |
    +--lib/
    |   +--sandbox-agent.jar
    |   +--sandbox-core.jar
    |   `--sandbox-spy.jar
    |
    +--provider/
    |.  `--sandbox-mgr-provider.jar
    |
    `--module/
      `--sandbox-mgr-module.jar

关键文件说明

  • ./sandbox/bin/sandbox.sh
    沙箱的客户端脚本,用于启动、管理沙箱

  • ./sandbox/cfg/
    沙箱配置文件存放目录,里面存放了沙箱的所有配置文件

    • ./sandbox/cfg/version
      存放沙箱容器的版本号,当你不确定当前容器的版本的时候可以通过这个文件进行核对

    • ./sandbox/cfg/sandbox.properties
      存放沙箱容器的配置信息,配置文件只会在沙箱容器启动的时候加载一次。他的详细配置解释可以单独见配置文件解释

    • ./sandbox/cfg/sandbox-logback.xml
      沙箱容器选用Logback作为他的日志框架,所以这里开放日志配置文件允许进行个性化的日志配置

  • ./sandbox/lib/
    沙箱主程序的库包目录,这里存放的是沙箱工程的主程序,不能随意的删除、改名和移动!

    文件名 作用说明
    sandbox-agent.jar 沙箱启动代理
    sandbox-core.jar 沙箱内核
    sandbox-spy.jar 沙箱间谍库,用于提供插桩埋点的间谍类

日志目录结构

沙箱采用的是Logback日志组件来完成日志记录,日志文件默认写入到${HOME}/logs/sandbox/sandbox.log文件中,如果你有需要也可以通过调整sandbox-logback.xml文件进行修改日志输出配置。

运行时文件

沙箱启动后将会创建一个隐藏文件${HOME}/.sandbox.token,这个文件将完成目标JVM进程和沙箱客户端进程一些信息的交互.

沙箱的模块目录

沙箱拥有两个加载模块的目录,用途各自不一

  • ./sandbox/module/

    沙箱系统模块目录,由配置项system_module进行定义。用于存放沙箱通用的管理模块,比如用于沙箱模块管理功能的module-mgr模块,未来的模块运行质量监控模块、安全校验模块也都将存放在此处,跟随沙箱的发布而分发。

    系统模块不受刷新(-f)、**强制刷新(-F)功能的影响,只有容器重置(-R)**能让沙箱重新加载系统模块目录下的所有模块。

  • ${HOME}/.sandbox-module/

    沙箱用户模块目录,由sandbox.properties的配置项user_module进行定义,默认为${HOME}/.sandbox-module/。一般用于存放用户自研的模块。自研的模块经常要面临频繁的版本升级工作,当需要进行模块动态热插拔替换的时候,可以通过**刷新(-f)强制刷新(-F)**来完成重新加载。

沙箱模块

  • 所有的沙箱模块都可以被设计成为热插拔

  • 一个JAR包下可以申明多个模块,模块需要符合Java SPI规范,要求

    • 必须拥有publish的无参构造函数
    • 必须实现com.alibaba.jvm.sandbox.api.Module接口
    • 必须完成META-INF/services/com.alibaba.jvm.sandbox.api.Module文件中的注册(Java SPI规范要求)

    也可以通过依赖sandbox-module-starter来简化以上操作。

    <parent>
    	<groupId>com.alibaba.jvm.sandbox</groupId>
    	<artifactId>sandbox-module-starter</artifactId>
    	<version>1.2.0</version>
    </parent> 
  • 同一个JAR包所声明的所有模块共享同一个ModuleJarClassLoader

  • 模块一共有四种状态

    • 加载
      模块被沙箱正确加载,沙箱将会允许模块进行命令相应、代码插桩等动作

    • 卸载
      沙箱不会再看到该模块,之前给该模块分配的所有资源都将会被回收,包括模块已经侦听事件的类都将会被移除掉侦听插桩,干净利落不留后遗症

    • 激活
      模块加载成功后默认是冻结状态,需要代码主动进行激活。模块只有在激活状态下才能监听到沙箱事件

    • 冻结
      模块进入到冻结状态之后,之前侦听的所有沙箱事件都将被屏蔽。需要注意的是,冻结的模块不会退回事件侦听的代码插桩,只有delete()、wathcing()或者模块被卸载的时候插桩代码才会被清理

配置文件解释

配置表

配置文件:./cfg/sandbox.properties

配置项 说明 默认值
system_module 系统模块本地路径 ./module
provider 强化服务提供包本地路径 ./provider
user_module 用户模块本地路径 ~/.sandbox-module
server.ip 沙箱HTTP服务IP 0.0.0.0
server.port 沙箱HTTP服务端口 0(随机端口)
unsafe.enable 是否允许增强JDK自带类 false

配置文件只会在沙箱第一次启动的时候加载,刷新(-f)、**强制刷新(-F)重置(-R)都不会让配置文件重新生效。如果希望配置文件重新生效,需要关闭(-S)**容器,重新再次加载。

重点配置解释

  • user_module

    用户模块本地路径是一个多值通配符表达式,如果用户模块散落在本地多个不同的路径下,可以通过配置多个路径(;分割),亦或者可以配置通配符表达式。

    例如,我们存在一种部署场景,多个用户模块在不同的路径上。

    # LOGGING_MODULE
    /home/upload/logging/jvm-sandbox-module/logging-module.jar
    
    # GREYS_MODULE
    /home/upload/greys/jvm-sandbox-module/greys-module.jar
    
    # MKAGENT_MODULE
    /home/upload/mkagent/jvm-sandbox-module/mkagent.jar
    • 使用多值配置:;分割
      user_module=/home/upload/logging/jvm-sandbox-module/logging-module.jar;/home/upload/greys/jvm-sandbox-module/greys-module.jar;/home/upload/mkagent/jvm-sandbox-module/mkagent.jar;

    • 使用通配符配置
      user_module=/home/upload/*/jvm-sandbox-module/*.jar

    以上两种配置都可以正确匹配到所期望三个模块的路径

  • unsafe.enable

    控制容器是否能让模块增强JDK自带的类,默认值为FALSE,即不允许增强JDK自带的类。但实际配置中,在自带的配置文件里,我主动将值配置为TRUE,将这个控制权交给USER_MODULE进行控制。

    配置值 说明
    TRUE 允许增强JDK自带类
    FALSE 不允许增强JDK自带类

命令说明

sandbox.sh是整个沙箱的主要操作客户端,通过HTTP协议来完成通讯,所以要求Linux系统必须安装curl命令。目前我使用的是BASH来实现,当然如果你有兴趣也可以自己用Python写一个脚本来实现玩玩~

  • -h:输出帮助信息

  • -p:指定目标JVM进程号

    操作的时候你只需要指定对应的JVM进程号即可,不用关心底层绑定的沙箱HTTP端口,sandbox.sh脚本会帮你搞定这件事

  • -v:输出加载到目标JVM中的沙箱版本信息

  • -l:列出目标JVM沙箱中已经加载的模块

  • -F:强制刷新用户模块

    沙箱容器在强制刷新的时候,首先会卸载当前所有已经被加载的用户模块,然后再重新对用户模块进行加载

    • 首先卸载掉所有已加载的用户模块,然后再重新进行加载

    • 模块卸载时将会释放掉沙箱为模块所有开启的资源

      • 模块打开的HTTP链接
      • 模块打开的WEBSOCKET链接
      • 模块打开所在的ModuleClassLoader
      • 模块进行的事件插桩
    • 当任何一个模块加载失败时,忽略该模块,继续加载其他可加载的模块

  • -f:刷新用户模块

    刷新用户模块,与强制刷新用户模块不同的地方是,普通刷新会遍历用户模块下所有发生改变的模块文件,当且仅对发生变化的文件进行重新加载操作。

    模块文件变化 刷新动作
    删除 卸载模块
    新增 加载模块
    更新 先卸载,再加载模块

    卸载模块的时候会释放沙箱为模块所开启的资源(同强制刷新),sandbox.properties不会被重新加载

    同样的是,sandbox.properties不会被重新加载

  • -R:沙箱模块重置

    沙箱模块重置的时候将会强制刷新所有的模块,包括用户模块系统模块

    同样的是,sandbox.properties不会被重新加载

  • -u:卸载指定模块

    卸载指定模块,支持通配符表达式子。卸载模块不会区分系统模块和用户模块,所有模块都可以通过这个参数完成卸载,所以切记不要轻易卸载module-mgr,否则你将失去模块管理功能,不然就只能-R来恢复了。

    EXAMPLE

    # 目标JVM进程号为4321,需要卸载的模块名为`debug-module`
    ./sandbox.sh -p 4321 -u 'debug-module'
    
    # 也可以使用通配符
    ./sandbox.sh -p 4321 -u 'debug-*'
    
  • -a:激活模块

    模块激活后才能受到沙箱事件

  • -A:冻结模块

    模块冻结后将感知不到任何沙箱事件,但对应的代码插桩还在。

  • -m:查看模块详细信息

    模块名需要精确匹配,不支持通配符。

    EXAMPLE

    # 目标JVM进程号为4321,需要观察的模块名为`sandbox-module-mgr`
    ./sandbox.sh -p 4321 -m 'sandbox-module-mgr'
          ID : sandbox-module-mgr
     VERSION : 0.0.1
      AUTHOR : luanjia@taobao.com
    JAR_FILE : /Users/luanjia/.opt/sandbox/lib/../module/sandbox-mgr-module.jar
       STATE : FROZEN
        MODE : {AGENT,ATTACH}
       CLASS : class com.albaba.jvm.sandbox.module.mgr.ModuleMgrModule
      LOADER : ModuleJarClassLoader[crc32=1721245995;file=/Users/luanjia/opt/sandbox/lib/../module/sandbox-mgr-module.jar;]
        cCnt : 0
        mCnt : 0
    
  • -d:模块自定义命令

    在模块中可以通过对方法标记@Command注释,让sandbox.sh可以将自定义命令传递给被标记的方法。

    @Information(id = "sandbox-info", version = "0.0.4", author = "luanjia@taobao.com")
    public class InfoModule implements Module {
    
        @Command("version")
        public void version(final PrintWriter writer) throws IOException {
            // ...
        }
    
    }

    此时对应过来的-d命令参数为:-d sandbox-info/version即可指定到这个方法。

LOGO

JVM沙箱偏向于底层产品,受众面比较窄,问题反馈沟通很可能会因为各种原因造成不及时。所以我们建立了一个钉钉小群,方便大家在这里进行沟通。

Clone this wiki locally