Skip to content

Commit

Permalink
[JENKINS-27392] Implement SimpleBuildWrapper in order to support Work…
Browse files Browse the repository at this point in the history
…flow project type .

This pull request integrates #3
  • Loading branch information
jglick authored and oleg-nenashev committed Oct 18, 2015
1 parent 153ec69 commit 453e8b5
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 19 deletions.
52 changes: 51 additions & 1 deletion pom.xml
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>1.420</version>
<version>1.609.1</version>
</parent>

<artifactId>mask-passwords</artifactId>
Expand All @@ -21,6 +21,13 @@
<tag>HEAD</tag>
</scm>

<licenses>
<license>
<name>MIT License</name>
<url>http://opensource.org/licenses/MIT</url>
</license>
</licenses>

<developers>
<developer>
<id>rseguy</id>
Expand All @@ -43,6 +50,49 @@
</pluginRepository>
</pluginRepositories>

<properties>
<workflow.version>1.8</workflow.version>
</properties>
<dependencies>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-job</artifactId>
<version>${workflow.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-basic-steps</artifactId>
<version>${workflow.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-cps</artifactId>
<version>${workflow.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-durable-task-step</artifactId>
<version>${workflow.version}</version>
<scope>test</scope>
</dependency>
<dependency> <!-- StepConfigTester -->
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-step-api</artifactId>
<version>${workflow.version}</version>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency> <!-- SemaphoreStep -->
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-support</artifactId>
<version>${workflow.version}</version>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
</dependencies>
</project>


Expand Up @@ -30,27 +30,31 @@
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import hudson.EnvVars;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.console.ConsoleLogFilter;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.ParameterValue;
import hudson.model.ParametersAction;
import hudson.tasks.BuildWrapper;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.tasks.BuildWrapperDescriptor;
import hudson.util.CopyOnWriteMap;
import hudson.util.Secret;

import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.tasks.SimpleBuildWrapper;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
Expand All @@ -64,7 +68,7 @@
*
* @author Romain Seguy (http://openromain.blogspot.com)
*/
public final class MaskPasswordsBuildWrapper extends BuildWrapper {
public final class MaskPasswordsBuildWrapper extends SimpleBuildWrapper {

private final List<VarPasswordPair> varPasswordPairs;

Expand All @@ -73,13 +77,8 @@ public MaskPasswordsBuildWrapper(List<VarPasswordPair> varPasswordPairs) {
this.varPasswordPairs = varPasswordPairs;
}

//TODO: Most probably the method is not required after introducing sensitive vars
/**
* This method is invoked before {@link #makeBuildVariables()} and {@link
* #setUp()}.
*/
@Override
public OutputStream decorateLogger(AbstractBuild build, OutputStream logger) {
public ConsoleLogFilter createLoggerDecorator(Run<?, ?> build) {
List<String> allPasswords = new ArrayList<String>(); // all passwords to be masked
MaskPasswordsConfig config = MaskPasswordsConfig.getInstance();

Expand All @@ -104,15 +103,42 @@ public OutputStream decorateLogger(AbstractBuild build, OutputStream logger) {
if(params != null) {
for(ParameterValue param : params) {
if(config.isMasked(param.getClass().getName())) {
String password = param.createVariableResolver(build).resolve(param.getName());
EnvVars env = new EnvVars();
param.buildEnvironment(build, env);
String password = env.get(param.getName());
if(StringUtils.isNotBlank(password)) {
allPasswords.add(password);
}
}
}
}

return new MaskPasswordsOutputStream(logger, allPasswords);
return new FilterImpl(allPasswords);
}

private static final class FilterImpl extends ConsoleLogFilter implements Serializable {

private static final long serialVersionUID = 1L;

private final List<Secret> allPasswords;

FilterImpl(List<String> allPasswords) {
this.allPasswords = new ArrayList<Secret>();
for (String password : allPasswords) {
this.allPasswords.add(Secret.fromString(password));
}
}

@SuppressWarnings("rawtypes")
@Override
public OutputStream decorateLogger(AbstractBuild _ignore, OutputStream logger) throws IOException, InterruptedException {
List<String> passwords = new ArrayList<String>();
for (Secret password : allPasswords) {
passwords.add(password.getPlainText());
}
return new MaskPasswordsOutputStream(logger, passwords);
}

}

/**
Expand Down Expand Up @@ -148,10 +174,8 @@ public void makeSensitiveBuildVariables(AbstractBuild build, Set<String> sensiti
}

@Override
public Environment setUp(AbstractBuild build, Launcher launcher, BuildListener listener) throws IOException, InterruptedException {
return new Environment() {
// nothing to tearDown()
};
public void setUp(Context context, Run<?, ?> build, FilePath workspace, Launcher launcher, TaskListener listener, EnvVars initialEnvironment) throws IOException, InterruptedException {
// nothing to do here
}

public List<VarPasswordPair> getVarPasswordPairs() {
Expand Down
Expand Up @@ -25,11 +25,12 @@
*/
package com.michelin.cio.hudson.plugins.passwordparam;

import hudson.EnvVars;
import hudson.model.AbstractBuild;
import hudson.model.ParameterValue;
import hudson.model.Run;
import hudson.util.Secret;
import hudson.util.VariableResolver;
import java.util.Map;
import org.kohsuke.stapler.DataBoundConstructor;

public class PasswordParameterValue extends ParameterValue {
Expand All @@ -49,7 +50,7 @@ public PasswordParameterValue(String name, String value, String description) {
}

@Override
public void buildEnvVars(AbstractBuild<?,?> build, Map<String,String> env) {
public void buildEnvironment(Run<?,?> build, EnvVars env) {
env.put(name.toUpperCase(), value != null ? Secret.toString(value) : null);
}

Expand Down
@@ -0,0 +1,123 @@
/*
* The MIT License
*
* Copyright 2015 Jesse Glick.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package com.michelin.cio.hudson.plugins.maskpasswords;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.apache.commons.io.FileUtils;
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.jenkinsci.plugins.workflow.steps.CoreWrapperStep;
import org.jenkinsci.plugins.workflow.steps.StepConfigTester;
import org.jenkinsci.plugins.workflow.test.steps.SemaphoreStep;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.runners.model.Statement;
import org.jvnet.hudson.test.BuildWatcher;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.RestartableJenkinsRule;

@Issue("JENKINS-27392")
public class MaskPasswordsWorkflowTest {

@ClassRule
public static BuildWatcher buildWatcher = new BuildWatcher();
@Rule
public RestartableJenkinsRule story = new RestartableJenkinsRule();

@Test
public void configRoundTrip() throws Exception {
story.addStep(new Statement() {
@Override
public void evaluate() throws Throwable {
MaskPasswordsBuildWrapper bw1 = new MaskPasswordsBuildWrapper(Collections.singletonList(new MaskPasswordsBuildWrapper.VarPasswordPair("PASSWORD", "s3cr3t")));
CoreWrapperStep step1 = new CoreWrapperStep(bw1);
CoreWrapperStep step2 = new StepConfigTester(story.j).configRoundTrip(step1);
MaskPasswordsBuildWrapper bw2 = (MaskPasswordsBuildWrapper) step2.getDelegate();
List<MaskPasswordsBuildWrapper.VarPasswordPair> pairs = bw2.getVarPasswordPairs();
assertEquals(1, pairs.size());
MaskPasswordsBuildWrapper.VarPasswordPair pair = pairs.get(0);
assertEquals("PASSWORD", pair.getVar());
assertEquals("s3cr3t", pair.getPassword());
}
});
}

@Test
public void basics() throws Exception {
story.addStep(new Statement() {
@Override
public void evaluate() throws Throwable {
WorkflowJob p = story.j.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition("node {wrap([$class: 'MaskPasswordsBuildWrapper', varPasswordPairs: [[var: 'PASSWORD', password: 's3cr3t']]]) {semaphore 'restarting'; echo 'printed s3cr3t oops'}}", true));
WorkflowRun b = p.scheduleBuild2(0).waitForStart();
SemaphoreStep.waitForStart("restarting/1", b);
}
});
story.addStep(new Statement() {
@Override
public void evaluate() throws Throwable {
WorkflowJob p = story.j.jenkins.getItemByFullName("p", WorkflowJob.class);
WorkflowRun b = p.getLastBuild();
assertEquals("TODO cannot keep it out of the closure block, but at least outside users cannot see this; withCredentials does better", new HashSet<String>(Arrays.asList("build.xml", "program.dat")), grep(b.getRootDir(), "s3cr3t"));
SemaphoreStep.success("restarting/1", null);
story.j.assertBuildStatusSuccess(story.j.waitForCompletion(b));
story.j.assertLogContains("printed ******** oops", b);
assertEquals("in build.xml only because it was literally in program text", Collections.singleton("build.xml"), grep(b.getRootDir(), "s3cr3t"));
}
});
}

// Copied from credentials-binding-plugin; perhaps belongs in JenkinsRule?
private static Set<String> grep(File dir, String text) throws IOException {
Set<String> matches = new TreeSet<String>();
grep(dir, text, "", matches);
return matches;
}
private static void grep(File dir, String text, String prefix, Set<String> matches) throws IOException {
File[] kids = dir.listFiles();
if (kids == null) {
return;
}
for (File kid : kids) {
String qualifiedName = prefix + kid.getName();
if (kid.isDirectory()) {
grep(kid, text, qualifiedName + "/", matches);
} else if (kid.isFile() && FileUtils.readFileToString(kid).contains(text)) {
matches.add(qualifiedName);
}
}
}

}

0 comments on commit 453e8b5

Please sign in to comment.