Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #33 from jenkinsci/trusted-classloader
[JENKINS-34650] Added a trusted classloader that runs CPS code outside sandbox
- Loading branch information
Showing
11 changed files
with
324 additions
and
56 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,49 @@ | |||
# Class loading for CPS Groovy execution | |||
Pipeline Script needs to be loaded into JVM for it to run. This is done | |||
by a `GroovyClassLoader` created inside `CpsGroovyShell`. | |||
|
|||
We create two classloaders in the following hierarchy | |||
|
|||
<<Jenkins UberClassLoader>> <-- <<trusted classloader>> <-- <<regular classloader>> | |||
|
|||
Jenkins uber classloader (`PluginManager.uberClassLoader`) defines | |||
visibility to every code in every plugins, so that every domain model | |||
of Jenkins can be accessed. This is the starting point of the hierarchy. | |||
|
|||
"Trusted classloader" (TCL) and "regular classloader" (RCL) are both | |||
`GroovyClassLoader`. Groovy compiler associated with them are configured for Jenkins Pipeline. | |||
For example, scripts loaded by those are CPS transformed to run with groovy-cps. | |||
See `CpsGroovyShellFactory` for more about the customizations that happen here. | |||
|
|||
Additionally, scripts loaded in RCL lives in the security sandbox | |||
(unless the user opts out of it, in which case it requires approval.) | |||
This classloader is meant to be used to load user-written Groovy scripts, | |||
which is not trusted. | |||
|
|||
Scripts loaded in TCL, OTOH, does not live in the security sandbox. This | |||
classloader is meant to be used to load Groovy code packaged inside | |||
plugins and global libraries. Write access to these sources should be | |||
restricted to `RUN_SCRIPTS` permission. | |||
|
|||
## Persisting code & surviving restarts | |||
When a Groovy script is loaded via one of `GroovyShell.parse*()` and | |||
`eval*()` methods, script text is captured and persisted as a part | |||
of the program state. This ensures that the exact same code is available | |||
when a pipeline execution resumes execution after a JVM restart. | |||
|
|||
This behaviour is desirable for the situation where script is supplied | |||
from outside and not readily available at the point of restart. | |||
For example, `Jenkinsfile` and scripts loaded from the `load` step | |||
fits this description. | |||
|
|||
It's also possible to augument `GroovyClassLoader` classpath via | |||
`addURL()` so that scripts are loaded on-demand whenever needed. | |||
This is more suitable for scripts that are considered a part of the | |||
system, such as global libraries or plugin code. | |||
|
|||
Those scripts are not snapshotted & persisted with the running program, | |||
so if they change while the program is running, with or without Jenkins restarts | |||
in the middle, then the program could fail. (For example, if a program | |||
call stack includes a class from a global library and that class goes away, | |||
then the program fails to survive the restart because the call stack cannot | |||
be deserialized. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
128 changes: 128 additions & 0 deletions
128
src/main/java/org/jenkinsci/plugins/workflow/cps/CpsGroovyShellFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,128 @@ | |||
package org.jenkinsci.plugins.workflow.cps; | |||
|
|||
import com.cloudbees.groovy.cps.CpsTransformer; | |||
import com.cloudbees.groovy.cps.NonCPS; | |||
import com.cloudbees.groovy.cps.SandboxCpsTransformer; | |||
import com.cloudbees.groovy.cps.TransformerConfiguration; | |||
import groovy.lang.GroovyShell; | |||
import jenkins.model.Jenkins; | |||
import org.codehaus.groovy.control.CompilerConfiguration; | |||
import org.codehaus.groovy.control.customizers.ImportCustomizer; | |||
import org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.GroovySandbox; | |||
|
|||
import javax.annotation.CheckForNull; | |||
import javax.annotation.Nullable; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
|
|||
/** | |||
* Instantiates {@link CpsGroovyShell}. | |||
* | |||
* @author Kohsuke Kawaguchi | |||
*/ | |||
class CpsGroovyShellFactory { | |||
private final @CheckForNull CpsFlowExecution execution; | |||
private boolean sandbox; | |||
private List<GroovyShellDecorator> decorators; | |||
private ClassLoader parent; | |||
|
|||
/** | |||
* @param execution | |||
* Instantiated {@link CpsGroovyShell} will be used to load scripts for this execution. | |||
*/ | |||
public CpsGroovyShellFactory(@Nullable CpsFlowExecution execution) { | |||
this.execution = execution; | |||
this.sandbox = execution!=null && execution.isSandbox(); | |||
this.decorators = GroovyShellDecorator.all(); | |||
} | |||
|
|||
private CpsGroovyShellFactory(CpsFlowExecution execution, boolean sandbox, ClassLoader parent, List<GroovyShellDecorator> decorators) { | |||
this.execution = execution; | |||
this.sandbox = sandbox; | |||
this.parent = parent; | |||
this.decorators = decorators; | |||
} | |||
|
|||
/** | |||
* Derives a new factory for creating trusted {@link CpsGroovyShell} | |||
*/ | |||
public CpsGroovyShellFactory forTrusted() { | |||
List<GroovyShellDecorator> inner = new ArrayList<>(); | |||
for (GroovyShellDecorator d : decorators) { | |||
inner.add(d.forTrusted()); | |||
} | |||
return new CpsGroovyShellFactory(execution, false, parent, inner); | |||
} | |||
|
|||
/** | |||
* Enables/disables the use of script-security on scripts loaded into {@link CpsGroovyShell}. | |||
* This method can be called to override the setting in {@link CpsFlowExecution}. | |||
*/ | |||
public CpsGroovyShellFactory withSandbox(boolean b) { | |||
this.sandbox = b; | |||
return this; | |||
} | |||
|
|||
public CpsGroovyShellFactory withParent(GroovyShell parent) { | |||
return withParent(parent.getClassLoader()); | |||
} | |||
|
|||
/** | |||
* Sets the parent classloader for the {@link CpsGroovyShell}. | |||
*/ | |||
public CpsGroovyShellFactory withParent(ClassLoader parent) { | |||
this.parent = parent; | |||
return this; | |||
} | |||
|
|||
private CpsTransformer makeCpsTransformer() { | |||
CpsTransformer t = sandbox ? new SandboxCpsTransformer() : new CpsTransformer(); | |||
t.setConfiguration(new TransformerConfiguration().withClosureType(CpsClosure2.class)); | |||
return t; | |||
} | |||
|
|||
private CompilerConfiguration makeConfig() { | |||
CompilerConfiguration cc = new CompilerConfiguration(); | |||
|
|||
cc.addCompilationCustomizers(makeImportCustomizer()); | |||
cc.addCompilationCustomizers(makeCpsTransformer()); | |||
cc.setScriptBaseClass(CpsScript.class.getName()); | |||
|
|||
for (GroovyShellDecorator d : decorators) { | |||
d.configureCompiler(execution,cc); | |||
} | |||
|
|||
return cc; | |||
} | |||
|
|||
private ImportCustomizer makeImportCustomizer() { | |||
ImportCustomizer ic = new ImportCustomizer(); | |||
ic.addStarImports(NonCPS.class.getPackage().getName()); | |||
ic.addStarImports("hudson.model","jenkins.model"); | |||
|
|||
for (GroovyShellDecorator d : decorators) { | |||
d.customizeImports(execution,ic); | |||
} | |||
return ic; | |||
} | |||
|
|||
private ClassLoader makeClassLoader() { | |||
Jenkins j = Jenkins.getInstance(); | |||
ClassLoader cl = j != null ? j.getPluginManager().uberClassLoader : CpsGroovyShell.class.getClassLoader(); | |||
return GroovySandbox.createSecureClassLoader(cl); | |||
} | |||
|
|||
public CpsGroovyShell build() { | |||
ClassLoader parent = this.parent; | |||
if (parent==null) | |||
parent = makeClassLoader(); | |||
|
|||
CpsGroovyShell shell = new CpsGroovyShell(parent, execution, makeConfig()); | |||
|
|||
for (GroovyShellDecorator d : decorators) { | |||
d.configureShell(execution,shell); | |||
} | |||
|
|||
return shell; | |||
} | |||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.