Override Compilation Customizer for Groovy Updater Scripts

This feature is available since Bloomreach Experience Manager 15.2.0.
This feature is available since Bloomreach Experience Manager 14.7.13

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.

Note that Bloomreach supports customizing the compilation customization used by Groovy but we do not support the customizations made by customers themselves: it is the customer's own responsibility that their customizers does not open up vulnerabilities with respect to invocations which should not be allowed from the Groovy Updater. When making use of the CompilationCustomizerFactory shown in the how-to and example below, the default Bloomreach compilation customizers are not used any more.

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
Did you find this page helpful?
How could this documentation serve you better?
On this page
    Did you find this page helpful?
    How could this documentation serve you better?