【问题标题】:How to disallow dependencies via @Grab in Groovy script如何在 Groovy 脚本中通过 @Grab 禁止依赖项
【发布时间】:2015-11-15 22:35:34
【问题描述】:

我希望允许用户在我的 Java 服务器应用程序中运行他们的 Groovy 脚本,但我也希望禁止他们使用 @Grab 添加任何随机依赖项。

是的,我可以通过在源代码中搜索和替换来简单地切断所有@Grab 注释,但最好以更优雅的方式执行此操作,例如仅允许已批准的依赖项。

是的,我知道这个问题的最佳解决方案是 JVM 的SecurityManager

【问题讨论】:

    标签: java security groovy scripting


    【解决方案1】:

    有多种方法,例如Groovy Sandbox,可能比您将要看到的效果更好。

    import groovy.grape.Grape
    
    Grape.metaClass.static.grab = {String endorsed ->
        throw new SecurityException("Oh no you didn't! Grabbing is forbidden.")
    }
    
    Grape.metaClass.static.grab = {Map dependency ->
        throw new SecurityException("Oh no you didn't! Grabbing is forbidden.")
    }
    
    Grape.metaClass.static.grab = {Map args, Map dependency ->
        throw new SecurityException("Oh no you didn't! Grabbing is forbidden.")
    }
    
    def source1 = '''
    println('This is a nice safe Groovy script.')
    '''
    
    def source2 = '''
    @Grab('commons-validator:commons-validator:1.4.1')
    
    import org.apache.commons.validator.routines.EmailValidator
    
    def emailValidator = EmailValidator.getInstance();
    
    assert emailValidator.isValid('what.a.shame@us.elections.gov')
    assert !emailValidator.isValid('an_invalid_emai_address')
    
    println 'You should not see this message!'
    '''
    
    def script
    def shell = new GroovyShell()
    
    try {
        script = shell.parse(source1)
        script.run()
    } catch (Exception e) { 
        assert false, "Oh, oh. That wasn't supposed to happen :("
    }    
    
    try {
        script = shell.parse(source2)
        assert false, "Oh, oh. That wasn't supposed to happen :("
    } catch (ExceptionInInitializerError e) { 
        println 'Naughty script was blocked when parsed.'
    }  
    

    上面的例子演示了如何阻止@Grab。它不是通过阻塞注解来做到这一点的,而是通过覆盖注解添加的方法调用:groovy.grape.Grape.grab()。

    Grape.metaClass.static.grab = {String endorsed ->
        throw new SecurityException("Oh no you didn't! Grabbing is forbidden.")
    }
    
    Grape.metaClass.static.grab = {Map dependency ->
        throw new SecurityException("Oh no you didn't! Grabbing is forbidden.")
    }
    
    Grape.metaClass.static.grab = {Map args, Map dependency ->
        throw new SecurityException("Oh no you didn't! Grabbing is forbidden.")
    }
    

    这是由 Groovy 控制台 AST 查看器剖析的 naughty 脚本:

    @groovy.lang.Grab(module = 'commons-validator', group = 'commons-validator', version = '1.4.1')
    import org.apache.commons.validator.routines.EmailValidator as EmailValidator
    
    public class script1440223706571 extends groovy.lang.Script { 
    
        private static org.codehaus.groovy.reflection.ClassInfo $staticClassInfo 
        public static transient boolean __$stMC 
    
        public script1440223706571() {
        }
    
        public script1440223706571(groovy.lang.Binding context) {
            super(context)
        }
    
        public static void main(java.lang.String[] args) {
            org.codehaus.groovy.runtime.InvokerHelper.runScript(script1440223706571, args)
        }
    
        public java.lang.Object run() {
            java.lang.Object emailValidator = org.apache.commons.validator.routines.EmailValidator.getInstance()
            assert emailValidator.isValid('what.a.shame@us.elections.gov') : null
            assert !(emailValidator.isValid('an_invalid_emai_address')) : null
            return null
        }
    
        static { 
            groovy.grape.Grape.grab([:], ['group': 'commons-validator', 'module': 'commons-validator', 'version': '1.4.1'])
        }
    
        protected groovy.lang.MetaClass $getStaticMetaClass() {
        }
    
    }
    

    在这里,您可以看到静态初始化程序中对 Grape.grab() 的调用。要添加对依赖项的细粒度过滤,您可以自省依赖项和背书参数。

    依赖

    ['group': 'commons-validator', 'module': 'commons-validator', 'version': '1.4.1']

    认可

    commons-validator:commons-validator:1.4.1

    修订实施

    这个新的实现使用拦截器来阻止/允许 Grape 抓取。

    import groovy.grape.GrapeIvy
    
    def source1 = '''
    println('This is a nice safe Groovy script.')
    '''
    
    def source2 = '''
    @Grab('commons-validator:commons-validator:1.4.1')
    
    import org.apache.commons.validator.routines.EmailValidator
    
    def emailValidator = EmailValidator.getInstance();
    
    assert emailValidator.isValid('what.a.shame@us.elections.gov')
    assert !emailValidator.isValid('an_invalid_emai_address')
    
    println 'You should not see this message!'
    '''
    
    def script
    def shell = new GroovyShell()
    def proxy = ProxyMetaClass.getInstance(GrapeIvy)
    
    proxy.interceptor = new GrapeInterceptor({group, module, version ->
        if(group == 'commons-validator' && module == 'commons-validator') false
        else true
    })
    
    proxy.use {
        shell.parse(source1).run()
    
        try {
            shell.parse(source2).run()
        } catch (org.codehaus.groovy.control.MultipleCompilationErrorsException e) {
            assert e.message.contains('unable to resolve class')
        }
    }
    
    @groovy.transform.TupleConstructor
    class GrapeInterceptor implements Interceptor {
        private boolean invokeMethod = true
        Closure authorizer
    
        def afterInvoke(Object object, String methodName, Object[] arguments, Object result) {
            invokeMethod = true
    
            return result
        }
    
        def beforeInvoke(Object object, String methodName, Object[] arguments) {
            if(methodName == 'createGrabRecord') {
                def dependencies = arguments[0]
                invokeMethod = authorizer(dependencies.group, dependencies.module, dependencies.version)
            } else {
                invokeMethod = true
            }
    
            return null
        }
    
        boolean doInvoke() { invokeMethod }
    }
    

    GrapeInterceptor 构造函数将闭包作为其唯一参数。使用此闭包,您可以轻松决定是否允许 Grab 发生:)

    例如,如果 Grab 看起来像这样:@Grab('commons-validator:commons-validator:1.4.1')

    闭包的参数分配如下:

    • 组:commons-validator
    • 模块:commons-validator
    • 版本:1.4.1

    要允许抓取,闭包必须返回 true。返回 false 以阻止它。

    【讨论】:

    • 很好,应该手工完成的工作太多,但仍然可以实现。谢谢。
    • 不客气。我更新了答案以包含更好的解决方案。一种允许您有选择地允许/阻止 Grabs。
    • 更好,谢谢!对这个解决方案的唯一抱怨是我必须知道太多关于GrapeIvy 实现的细节,即createGrabRecord 方法,但肯定更好并且可以使用。 ProxyMetaClass有什么魔力!
    • 不客气。 ProxyMetaClass 确实很棒。我不确定你必须知道太多细节是什么意思。 (自定义)GrapeInterceptor 处理细节。您需要做的就是创建它的一个实例,然后将它与代理一起使用。您甚至可以创建工厂类或方法来返回可供使用的代理。
    猜你喜欢
    • 2020-04-08
    • 1970-01-01
    • 2016-11-01
    • 2013-09-22
    • 2016-09-21
    • 1970-01-01
    • 1970-01-01
    • 2020-09-04
    • 2017-05-15
    相关资源
    最近更新 更多