Skip to content

Commit 77f0e8b

Browse files
committed
[SECURITY-2705]
1 parent 37cea9a commit 77f0e8b

File tree

4 files changed

+135
-7
lines changed

4 files changed

+135
-7
lines changed

src/main/java/org/jenkinsci/plugins/workflow/support/steps/input/InputStep.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package org.jenkinsci.plugins.workflow.support.steps.input;
22

33
import hudson.Extension;
4+
import hudson.ExtensionList;
45
import hudson.Util;
6+
import hudson.model.FileParameterDefinition;
57
import hudson.model.ParameterDefinition;
68
import hudson.model.PasswordParameterDefinition;
79
import hudson.model.Run;
810
import hudson.model.TaskListener;
11+
import hudson.model.ParameterDefinition.ParameterDescriptor;
912
import hudson.util.Secret;
1013
import java.io.Serializable;
1114
import java.util.Collections;
@@ -247,5 +250,14 @@ private static <T> Map<String, Object> copyMapReplacingEntry(Map<String, ?> map,
247250
}
248251
return newMap;
249252
}
253+
254+
/** For the pipeline syntax generator page. */
255+
public List<ParameterDescriptor> getParametersDescriptors() {
256+
// See SECURITY-2705 on why we ban FileParemeterDefinition
257+
return ExtensionList.lookup(ParameterDescriptor.class).stream().
258+
filter(descriptor -> descriptor.clazz != FileParameterDefinition.class).
259+
collect(Collectors.toList());
260+
}
250261
}
262+
251263
}

src/main/java/org/jenkinsci/plugins/workflow/support/steps/input/InputStepExecution.java

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
import com.cloudbees.plugins.credentials.CredentialsParameterValue;
44
import com.cloudbees.plugins.credentials.builds.CredentialsParameterBinder;
5+
import hudson.AbortException;
56
import hudson.FilePath;
67
import hudson.Util;
78
import hudson.console.HyperlinkNote;
89
import hudson.model.Failure;
10+
import hudson.model.FileParameterDefinition;
911
import hudson.model.FileParameterValue;
1012
import hudson.model.Job;
1113
import hudson.model.ModelObject;
@@ -29,6 +31,8 @@
2931
import org.jenkinsci.plugins.workflow.steps.AbstractStepExecutionImpl;
3032
import org.jenkinsci.plugins.workflow.support.actions.PauseAction;
3133
import org.jenkinsci.plugins.workflow.graph.FlowNode;
34+
import org.kohsuke.accmod.Restricted;
35+
import org.kohsuke.accmod.restrictions.NoExternalUse;
3236
import org.kohsuke.stapler.HttpResponse;
3337
import org.kohsuke.stapler.StaplerRequest;
3438
import org.kohsuke.stapler.interceptor.RequirePOST;
@@ -45,6 +49,8 @@
4549
import java.util.concurrent.TimeoutException;
4650
import java.util.logging.Level;
4751
import java.util.logging.Logger;
52+
53+
import jenkins.util.SystemProperties;
4854
import jenkins.util.Timer;
4955
import org.jenkinsci.plugins.workflow.steps.FlowInterruptedException;
5056
import org.jenkinsci.plugins.workflow.steps.StepContext;
@@ -56,6 +62,13 @@ public class InputStepExecution extends AbstractStepExecutionImpl implements Mod
5662

5763
private static final Logger LOGGER = Logger.getLogger(InputStepExecution.class.getName());
5864

65+
// for testing only
66+
static final String UNSAFE_PARAMETER_ALLOWED_PROPERTY_NAME = InputStepExecution.class.getName() + ".supportUnsafeParameters";
67+
68+
private static boolean isAllowUnsafeParameters() {
69+
return SystemProperties.getBoolean(UNSAFE_PARAMETER_ALLOWED_PROPERTY_NAME);
70+
}
71+
5972
/**
6073
* Result of the input.
6174
*/
@@ -70,6 +83,20 @@ public class InputStepExecution extends AbstractStepExecutionImpl implements Mod
7083

7184
@Override
7285
public boolean start() throws Exception {
86+
// SECURITY-2705 if the escape hatch is allowed just warn about pending removal, otherwise fail the build before waiting
87+
if (getHasUnsafeParameters()) {
88+
if (isAllowUnsafeParameters()) {
89+
getListener().getLogger().println("Support for FileParameters in the input step has been enabled via "
90+
+ UNSAFE_PARAMETER_ALLOWED_PROPERTY_NAME + " which will be removed in a future release." +
91+
System.lineSeparator() +
92+
"Details on how to migrate your pipeline can be found online: https://jenkins.io/redirect/plugin/pipeline-input-step/file-parameters.");
93+
} else {
94+
throw new AbortException("Support for FileParameters in the input step is disabled and will be removed in a future release. " +
95+
System.lineSeparator() + "Details on how to migrate your pipeline can be found online: " +
96+
"https://jenkins.io/redirect/plugin/pipeline-input-step/file-parameters.");
97+
}
98+
}
99+
73100
Run<?, ?> run = getRun();
74101
TaskListener listener = getListener();
75102
FlowNode node = getNode();
@@ -406,15 +433,28 @@ private Map<String,Object> parseValue(StaplerRequest request) throws ServletExce
406433
}
407434

408435
private Object convert(String name, ParameterValue v) throws IOException, InterruptedException {
409-
if (v instanceof FileParameterValue) {
410-
FileParameterValue fv = (FileParameterValue) v;
411-
FilePath fp = new FilePath(getRun().getRootDir()).child(name);
412-
fp.copyFrom(fv.getFile());
413-
return fp;
436+
if (v instanceof FileParameterValue) { // SECURITY-2705
437+
if (isAllowUnsafeParameters()) {
438+
FileParameterValue fv = (FileParameterValue) v;
439+
FilePath fp = new FilePath(getRun().getRootDir()).child(name);
440+
fp.copyFrom(fv.getFile());
441+
return fp;
442+
} else {
443+
// whilst the step would be aborted in start() if the pipeline was in the input step at the point of
444+
// upgrade it will be allowed to pass so we pick it up here.
445+
throw new AbortException("Support for FileParameters in the input step is disabled and will be removed in a future release. " +
446+
System.lineSeparator() + "Details on how to migrate your pipeline can be found online: " +
447+
"https://jenkins.io/redirect/plugin/pipeline-input-step/file-parameters.");
448+
}
414449
} else {
415450
return v.getValue();
416451
}
417452
}
418453

454+
@Restricted(NoExternalUse.class) // jelly access only
455+
public boolean getHasUnsafeParameters() {
456+
return input.getParameters().stream().anyMatch(parameter -> parameter.getClass() == FileParameterDefinition.class);
457+
}
458+
419459
private static final long serialVersionUID = 1L;
420460
}

src/main/resources/org/jenkinsci/plugins/workflow/support/steps/input/InputStepExecution/index.jelly

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@
77
<h1>${it.input.message}</h1>
88
<j:if test="${!it.completed}">
99
<f:form method="post" action="${it.id}/submit" name="${it.id}">
10+
<j:if test="${it.hasUnsafeParameters}">
11+
<div class="alert alert-warning">
12+
Support for <code>FileParameter</code>s will be removed in a future release.
13+
Details on how to migrate your pipeline can be found
14+
<a rel="noopener noreferrer" href="https://jenkins.io/redirect/plugin/pipeline-input-step/file-parameters">online</a>.
15+
</div>
16+
</j:if>
1017
<j:forEach var="param" items="${it.input.parameters}">
1118
<j:set var="escapeEntryTitleAndDescription" value="true"/>
1219
<st:include page="index.jelly" it="${param}"/>

src/test/java/org/jenkinsci/plugins/workflow/support/steps/input/InputStepTest.java

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import com.gargoylesoftware.htmlunit.WebRequest;
3535
import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
3636
import com.gargoylesoftware.htmlunit.html.HtmlElementUtil;
37+
import com.gargoylesoftware.htmlunit.html.HtmlFileInput;
3738
import com.gargoylesoftware.htmlunit.html.HtmlForm;
3839
import com.gargoylesoftware.htmlunit.html.HtmlPage;
3940
import com.google.common.base.Predicate;
@@ -48,12 +49,14 @@
4849
import hudson.model.queue.QueueTaskFuture;
4950

5051

52+
import java.io.File;
5153
import java.io.IOException;
5254

5355
import hudson.security.ACL;
5456
import hudson.security.ACLContext;
5557
import hudson.util.Secret;
5658
import jenkins.model.IdStrategy;
59+
import jenkins.model.InterruptedBuildAction;
5760
import jenkins.model.Jenkins;
5861
import org.apache.commons.lang.StringUtils;
5962
import org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl;
@@ -63,11 +66,11 @@
6366
import org.jenkinsci.plugins.workflow.graphanalysis.DepthFirstScanner;
6467
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
6568
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
66-
import org.junit.Assert;
6769
import org.junit.ClassRule;
6870
import org.junit.Rule;
6971
import org.junit.Test;
7072
import org.jvnet.hudson.test.BuildWatcher;
73+
import org.jvnet.hudson.test.FlagRule;
7174
import org.jvnet.hudson.test.Issue;
7275
import org.jvnet.hudson.test.JenkinsRule;
7376

@@ -80,15 +83,28 @@
8083
import edu.umd.cs.findbugs.annotations.Nullable;
8184
import org.jvnet.hudson.test.recipes.LocalData;
8285

86+
import static org.hamcrest.MatcherAssert.assertThat;
87+
import static org.hamcrest.Matchers.allOf;
88+
import static org.hamcrest.Matchers.containsString;
89+
import static org.hamcrest.Matchers.hasItem;
90+
import static org.hamcrest.Matchers.instanceOf;
91+
import static org.hamcrest.Matchers.not;
92+
import static org.junit.Assert.assertEquals;
93+
import static org.junit.Assert.assertFalse;
94+
import static org.junit.Assert.assertTrue;
95+
import static org.junit.Assert.assertNotNull;
96+
8397
/**
8498
* @author Kohsuke Kawaguchi
8599
*/
86-
public class InputStepTest extends Assert {
100+
public class InputStepTest {
87101
@Rule public JenkinsRule j = new JenkinsRule();
88102

89103
@ClassRule
90104
public static BuildWatcher buildWatcher = new BuildWatcher();
91105

106+
@Rule public FlagRule<String> allowUnsafeParams = FlagRule.systemProperty(InputStepExecution.UNSAFE_PARAMETER_ALLOWED_PROPERTY_NAME, null);
107+
92108
/**
93109
* Try out a parameter.
94110
*/
@@ -475,6 +491,59 @@ public void passwordParameters() throws Exception {
475491
j.assertLogContains("Password is mySecret", b);
476492
}
477493

494+
@Issue("SECURITY-2705")
495+
@Test
496+
public void fileParameterWithEscapeHatch() throws Exception {
497+
System.setProperty(InputStepExecution.UNSAFE_PARAMETER_ALLOWED_PROPERTY_NAME, "true");
498+
WorkflowJob foo = j.jenkins.createProject(WorkflowJob.class, "foo");
499+
foo.setDefinition(new CpsFlowDefinition("node {\n" +
500+
"input message: 'Please provide a file', parameters: [file('paco.txt')], id: 'Id' \n" +
501+
" }",true));
502+
503+
// get the build going, and wait until workflow pauses
504+
QueueTaskFuture<WorkflowRun> q = foo.scheduleBuild2(0);
505+
WorkflowRun b = q.waitForStart();
506+
j.waitForMessage("Input requested", b);
507+
508+
InputAction action = b.getAction(InputAction.class);
509+
assertEquals(1, action.getExecutions().size());
510+
511+
// submit the input, and expect a failure, no need to set any file value as the check we are testing takes
512+
// place before we try to interact with the file
513+
JenkinsRule.WebClient wc = j.createWebClient();
514+
HtmlPage p = wc.getPage(b, action.getUrlName());
515+
HtmlForm f = p.getFormByName("Id");
516+
HtmlFileInput fileInput = f.getInputByName("file");
517+
fileInput.setValueAttribute("dummy.txt");
518+
fileInput.setContentType("text/csv");
519+
String currentTime = "Current time " + System.currentTimeMillis();
520+
fileInput.setData(currentTime.getBytes());
521+
j.submit(f, "proceed");
522+
523+
j.assertBuildStatus(Result.SUCCESS, j.waitForCompletion(b));
524+
assertTrue(new File(b.getRootDir(), "paco.txt").exists());
525+
assertThat(JenkinsRule.getLog(b),
526+
allOf(containsString(InputStepExecution.UNSAFE_PARAMETER_ALLOWED_PROPERTY_NAME),
527+
containsString("will be removed in a future release"),
528+
containsString("https://jenkins.io/redirect/plugin/pipeline-input-step/file-parameters")));
529+
}
530+
531+
@Issue("SECURITY-2705")
532+
@Test
533+
public void fileParameterShouldFailAtRuntime() throws Exception {
534+
WorkflowJob foo = j.jenkins.createProject(WorkflowJob.class, "foo");
535+
foo.setDefinition(new CpsFlowDefinition("input message: 'Please provide a file', parameters: [file('paco.txt')], id: 'Id'",true));
536+
537+
// get the build going, and wait until workflow pauses
538+
QueueTaskFuture<WorkflowRun> q = foo.scheduleBuild2(0);
539+
WorkflowRun b = q.waitForStart();
540+
541+
j.assertBuildStatus(Result.FAILURE, j.waitForCompletion(b));
542+
assertThat(JenkinsRule.getLog(b),
543+
allOf(not(containsString(InputStepExecution.UNSAFE_PARAMETER_ALLOWED_PROPERTY_NAME)),
544+
containsString("https://jenkins.io/redirect/plugin/pipeline-input-step/file-parameters")));
545+
}
546+
478547
@LocalData
479548
@Test public void serialForm() throws Exception {
480549
WorkflowJob p = j.jenkins.getItemByFullName("p", WorkflowJob.class);

0 commit comments

Comments
 (0)