Skip to content
Permalink
Browse files

Merge pull request #15 from dheid/master

JENKINS-43637 Secures groovy script execution
  • Loading branch information...
dheid committed Nov 2, 2017
2 parents cdceece + 71e3782 commit 3d33744ba1c459beb8e6c46c1112ab236b18a699
@@ -3,3 +3,4 @@ work
*.iml
*.ipr
*.iws
.idea
29 pom.xml
@@ -4,7 +4,7 @@
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>1.501</version>
<version>2.37</version>
</parent>

<artifactId>postbuildscript</artifactId>
@@ -28,34 +28,21 @@
</developer>
</developers>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.6</maven.compiler.source>
<maven.compiler.target>1.6</maven.compiler.target>
<ivy.plugin.version>1.19</ivy.plugin.version>
</properties>

<scm>
<connection>scm:git:git://github.com/jenkinsci/postbuildscript-plugin.git</connection>
<developerConnection>scm:git:git@github.com:jenkinsci/postbuildscript-plugin.git</developerConnection>
<tag>HEAD</tag>
</scm>
</scm>

<dependencies>

<dependency>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>maven-plugin</artifactId>
</dependency>

<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>ivy</artifactId>
<version>${ivy.plugin.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>matrix-project</artifactId>
<version>1.12</version>
</dependency>

</dependencies>
</dependencies>

<repositories>
<repository>
@@ -5,7 +5,6 @@
import hudson.Launcher;
import hudson.Util;
import hudson.matrix.*;
import hudson.maven.MavenModuleSet;
import hudson.model.*;
import hudson.tasks.*;
import org.jenkinsci.plugins.postbuildscript.service.ScriptExecutor;
@@ -317,10 +316,7 @@ public String getHelpFile() {

@Override
public boolean isApplicable(Class<? extends AbstractProject> jobType) {
return Project.class.isAssignableFrom(jobType)
|| MatrixProject.class.isAssignableFrom(jobType)
|| MavenModuleSet.class.isAssignableFrom(jobType)
|| (Hudson.getInstance().getPlugin("ivy") != null && hudson.ivy.IvyModuleSet.class.isAssignableFrom(jobType));
return true;
}

public boolean isMatrixProject(Object job) {

This file was deleted.

@@ -1,29 +1,34 @@
package org.jenkinsci.plugins.postbuildscript.service;

import groovy.lang.GroovyShell;
import groovy.lang.Binding;
import hudson.EnvVars;
import hudson.FilePath;
import hudson.Launcher;
import hudson.Util;
import hudson.model.BuildListener;
import hudson.remoting.Callable;
import hudson.remoting.VirtualChannel;
import hudson.tasks.BatchFile;
import hudson.tasks.CommandInterpreter;
import hudson.tasks.Shell;
import jenkins.SlaveToMasterFileCallable;
import jenkins.security.SlaveToMasterCallable;
import org.jenkinsci.plugins.postbuildscript.PostBuildScriptException;
import org.jenkinsci.plugins.postbuildscript.PostBuildScriptLog;
import org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
* @author Gregory Boissinot
*/
public class ScriptExecutor implements Serializable {

protected PostBuildScriptLog log;
private PostBuildScriptLog log;

private BuildListener listener;

@@ -32,46 +37,44 @@ public ScriptExecutor(PostBuildScriptLog log, BuildListener listener) {
this.listener = listener;
}

public int executeScriptPathAndGetExitCode(FilePath workspace, String scriptFilePath, Launcher launcher) throws PostBuildScriptException {
public int executeScriptPathAndGetExitCode(FilePath workspace, String command, Launcher launcher) throws PostBuildScriptException {

if (scriptFilePath == null) {
throw new NullPointerException("The scriptFilePath object must be set.");
FilePath filePath = resolveScriptPath(workspace, command);
String[] splittedCommand = command.split("\\s+");
String[] parameters;
parameters = new String[splittedCommand.length - 1];
if (splittedCommand.length > 1) {
System.arraycopy(splittedCommand, 1, parameters, 0, parameters.length);
}
return executeScript(workspace, filePath, launcher, parameters);

FilePath filePath = getFilePath(workspace, scriptFilePath);
if (filePath == null) {
throw new PostBuildScriptException(String.format("The script file path '%s' doesn't exist.", scriptFilePath));
}

return executeScript(workspace, filePath, launcher);
}

private String getResolvedContentWithEnvVars(FilePath filePath) throws PostBuildScriptException {
String scriptContentResolved;
try {
log.info("Resolving environment variables for the script content.");
scriptContentResolved =
filePath.act(new FilePath.FileCallable<String>() {
filePath.act(new SlaveToMasterFileCallable<String>() {
@Override
public String invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
String scriptContent = Util.loadFile(f);
return Util.replaceMacro(scriptContent, EnvVars.masterEnvVars);
}
});
} catch (IOException ioe) {
} catch (IOException | InterruptedException ioe) {
throw new PostBuildScriptException("Error to resolve environment variables", ioe);
} catch (InterruptedException ie) {
throw new PostBuildScriptException("Error to resolve environment variables", ie);
}
return scriptContentResolved;
}

private int executeScript(FilePath workspace, FilePath script, final Launcher launcher) throws PostBuildScriptException {
private int executeScript(FilePath workspace, FilePath script, final Launcher launcher, String[] parameters) throws PostBuildScriptException {

assert script != null;
assert launcher != null;

String scriptContent = getResolvedContentWithEnvVars(script);
log.info(String.format("Evaluating the script: \n %s", scriptContent));
log.info(String.format("Executing the script %s with parameters %s", script, Arrays.toString(parameters)));
FilePath tmpFile;
try {
final CommandInterpreter batchRunner;
@@ -81,19 +84,20 @@ private int executeScript(FilePath workspace, FilePath script, final Launcher la
batchRunner = new BatchFile(scriptContent);
}
tmpFile = batchRunner.createScriptFile(workspace);
return launcher.launch().cmds(batchRunner.buildCommandLine(tmpFile)).stdout(listener).pwd(workspace).join();
} catch (InterruptedException ie) {
List<String> args = new ArrayList<>(Arrays.asList(batchRunner.buildCommandLine(tmpFile)));
args.addAll(Arrays.asList(parameters));
return launcher.launch().cmds(args).stdout(listener).pwd(workspace).join();
} catch (InterruptedException | IOException ie) {
throw new PostBuildScriptException("Error to execute script", ie);
} catch (IOException ioe) {
throw new PostBuildScriptException("Error to execute script", ioe);
}
}


private FilePath getFilePath(final FilePath workspace, final String givenPath) throws PostBuildScriptException {

try {
return workspace.act(new FilePath.FileCallable<FilePath>() {
return workspace.act(new SlaveToMasterFileCallable<FilePath>() {
@Override
public FilePath invoke(File ws, VirtualChannel channel) throws IOException, InterruptedException {
File givenFile = new File(givenPath);
if (givenFile.exists()) {
@@ -107,10 +111,8 @@ public FilePath invoke(File ws, VirtualChannel channel) throws IOException, Inte
return null;
}
});
} catch (IOException ioe) {
} catch (IOException | InterruptedException ioe) {
throw new PostBuildScriptException("Error to resolve script path", ioe);
} catch (InterruptedException ie) {
throw new PostBuildScriptException("Error to resolve script path", ie);
}
}

@@ -121,24 +123,22 @@ public boolean performGroovyScript(final FilePath workspace, final String script
throw new NullPointerException("The script content object must be set.");
}
try {
return workspace.act(new Callable<Boolean, Throwable>() {
return workspace.act(new SlaveToMasterCallable<Boolean, Throwable>() {
@Override
public Boolean call() throws Throwable {
final String groovyExpressionResolved = Util.replaceMacro(scriptContent, EnvVars.masterEnvVars);
log.info(String.format("Evaluating the groovy script: \n %s", scriptContent));
GroovyShell shell = new GroovyShell();
shell.setVariable("workspace", new File(workspace.getRemote()));
shell.setVariable("log", log);
shell.setVariable("out", log.getListener().getLogger());
shell.evaluate(groovyExpressionResolved);
Binding binding = new Binding();
binding.setVariable("workspace", new File(workspace.getRemote()));
binding.setVariable("log", log);
binding.setVariable("out", log.getListener().getLogger());
ClassLoader classLoader = getClass().getClassLoader();
SecureGroovyScript script = new SecureGroovyScript(groovyExpressionResolved, false, null);
script.configuringWithNonKeyItem();
script.evaluate(classLoader, binding);
return true;
}
});
} catch (IOException ioe) {
listener.getLogger().print("Problems occurs: " + ioe.getMessage());
return false;
} catch (InterruptedException ie) {
listener.getLogger().print("Problems occurs: " + ie.getMessage());
return false;
} catch (Throwable e) {
listener.getLogger().print("Problems occurs: " + e.getMessage());
return false;
@@ -147,16 +147,23 @@ public Boolean call() throws Throwable {


public boolean performGroovyScriptFile(FilePath workspace, final String scriptFilePath) throws PostBuildScriptException {
if (scriptFilePath == null) {
throw new NullPointerException("The scriptFilePath object must be set.");
FilePath filePath = resolveScriptPath(workspace, scriptFilePath);

String scriptContent = getResolvedContentWithEnvVars(filePath);
return performGroovyScript(workspace, scriptContent);
}

private FilePath resolveScriptPath(FilePath workspace, String commandString) throws PostBuildScriptException {
if (commandString == null) {
throw new NullPointerException("The commandString object must be set.");
}

String scriptFilePath = commandString.split("\\s+")[0];

FilePath filePath = getFilePath(workspace, scriptFilePath);
if (filePath == null) {
throw new PostBuildScriptException(String.format("The script file path '%s' doesn't exist.", scriptFilePath));
}

String scriptContent = getResolvedContentWithEnvVars(filePath);
return performGroovyScript(workspace, scriptContent);
return filePath;
}
}

0 comments on commit 3d33744

Please sign in to comment.
You can’t perform that action at this time.