Override Compilation Customizer for Groovy Updater Scripts
Introduction
Goal
Customize the compilation process to achieve custom security goals for Groovy Updater Scripts by overriding the default compilation customizers.
Background
The Updater Editor allows developers to create, manage, and run Groovy updater scripts against a running repository from within the Bloomreach Experience Manager UI. Groovy provides the concept of compilation customizers which can be used to tweak the compilation process.
In Bloomreach Experience Manager versions 14.7.13 and 15.2.0 and newer, it is possible to replace the default compilation customizers with project-specific compilation customizers. The project-specific compilation customizers can reuse Bloomreach configuration through some public static variables and methods in org.onehippo.repository.update.GroovyUpdaterClassLoader.
How To
The following instructions assume a standard implementation project structure based on the Maven archetype and package names starting with org.myproject (replace with names relevant for your organization and project).
To override the default Groovy compilation customizers, make the following changes in your implementation project's cms module:
Add a file spring.factories in src/main/resources/META-INF:
cms/src/main/resources/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration = org.myproject.spring.MyAppConfiguration
Add a Java class org.myproject.spring.MyAppConfiguration:
cms/src/main/java/org/myproject/spring/MyAppConfiguration.java
package org.myproject.spring; import org.myproject.groovy.CompilationCustomizerFactoryImpl; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MyAppConfiguration { @Bean(initMethod = "init", destroyMethod = "destroy") public CompilationCustomizerFactoryImpl getCompilationCustomizerFactory() { return new CompilationCustomizerFactoryImpl(); } }
Add a Java class org.myproject.groovy.CompilationCustomizerFactoryImpl:
cms/src/main/java/org/myproject/groovy/CompilationCustomizerFactoryImpl.java
package org.myproject.groovy; import static java.util.Collections.unmodifiableList; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; import org.codehaus.groovy.control.customizers.CompilationCustomizer; import org.onehippo.cms7.services.HippoServiceRegistry; import org.onehippo.repository.update.CompilationCustomizerFactory; public class CompilationCustomizerFactoryImpl implements CompilationCustomizerFactory { public static final List<String> myAllowedImports = unmodifiableList( Stream.of("org.onehippo.repository.update.BaseNodeUpdateVisitor", "javax.jcr.Node", "javax.jcr.RepositoryException", "javax.jcr.Session").collect(Collectors.toList())); public void init() { HippoServiceRegistry.register(this, CompilationCustomizerFactory.class); } public void destroy() { HippoServiceRegistry.unregister(this, CompilationCustomizerFactory.class); } @Override public CompilationCustomizer[] createCompilationCustomizers() { // TODO return your custom compilation customizers } }
Finally, implement the createCompilationCustomizers method (see below for an example), then build and run your project.
Example
The default compilation customizers implementation in org.onehippo.repository.update.GroovyUpdaterClassLoader uses a list of disallowed imports. The example below replaces the default implementation with an one that uses a list of allowed imports instead.
package org.myproject.groovy; import static java.util.Collections.unmodifiableList; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; import org.codehaus.groovy.control.customizers.CompilationCustomizer; import org.codehaus.groovy.control.customizers.SecureASTCustomizer; import org.onehippo.cms7.services.HippoServiceRegistry; import org.onehippo.repository.update.CompilationCustomizerFactory; import org.onehippo.repository.update.GroovyUpdaterClassLoader; public class CompilationCustomizerFactoryImpl implements CompilationCustomizerFactory { public static final List<String> myAllowedImports = unmodifiableList( Stream.of("org.onehippo.repository.update.BaseNodeUpdateVisitor", "javax.jcr.Node", "javax.jcr.RepositoryException", "javax.jcr.Session").collect(Collectors.toList())); public void init() { HippoServiceRegistry.register(this, CompilationCustomizerFactory.class); } public void destroy() { HippoServiceRegistry.unregister(this, CompilationCustomizerFactory.class); } @Override public CompilationCustomizer[] createCompilationCustomizers() { final SecureASTCustomizer compilationCustomizer = new SecureASTCustomizer(); compilationCustomizer.setAllowedImports(myAllowedImports); compilationCustomizer.setIndirectImportCheckEnabled(true); compilationCustomizer.addExpressionCheckers(new GroovyUpdaterClassLoader.DefaultUpdaterExpressionChecker()); return new CompilationCustomizer[] { GroovyUpdaterClassLoader.createDefaultImportCustomizer(), compilationCustomizer }; } }
Add more classes to myAllowedImports as needed.
Any updater script that imports a class not on the allowed imports list will result in an error similar to the one below:
java.lang.SecurityException: Importing [my.package.MyClass] is not allowed