Skip to content

Commit 84b1852

Browse files
committed
[JENKINS-46795] Abort builds with untrusted Jenkinsfile, but only given passive cause
1 parent 1d80e65 commit 84b1852

File tree

3 files changed

+58
-5
lines changed

3 files changed

+58
-5
lines changed

src/main/java/org/jenkinsci/plugins/workflow/multibranch/ReadTrustedStep.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ public static class Execution extends AbstractSynchronousNonBlockingStepExecutio
177177
}
178178
build.addAction(new SCMRevisionAction(scmSource, tip));
179179
}
180-
SCMRevision trusted = scmSource.getTrustedRevision(tip, listener);
180+
SCMRevision trusted = SCMBinder.getTrustedRevision(scmSource, tip, listener, build);
181181
boolean trustCheck = !tip.equals(trusted);
182182
String untrustedFile = null;
183183
String content;

src/main/java/org/jenkinsci/plugins/workflow/multibranch/SCMBinder.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,22 @@
2929
import hudson.Extension;
3030
import hudson.Functions;
3131
import hudson.model.Action;
32+
import hudson.model.Cause;
3233
import hudson.model.Descriptor;
3334
import hudson.model.DescriptorVisibilityFilter;
3435
import hudson.model.ItemGroup;
3536
import hudson.model.Queue;
37+
import hudson.model.Run;
3638
import hudson.model.TaskListener;
3739
import hudson.scm.SCM;
40+
import hudson.triggers.SCMTrigger;
41+
import hudson.triggers.TimerTrigger;
3842
import java.io.IOException;
3943
import java.util.List;
44+
import java.util.Set;
4045
import jenkins.branch.Branch;
46+
import jenkins.branch.BranchEventCause;
47+
import jenkins.branch.BranchIndexingCause;
4148
import jenkins.scm.api.SCMFileSystem;
4249
import jenkins.scm.api.SCMHead;
4350
import jenkins.scm.api.SCMRevision;
@@ -46,6 +53,7 @@
4653
import jenkins.util.SystemProperties;
4754
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
4855
import org.jenkinsci.plugins.workflow.cps.CpsScmFlowDefinition;
56+
import org.jenkinsci.plugins.workflow.cps.replay.ReplayCause;
4957
import org.jenkinsci.plugins.workflow.flow.FlowDefinition;
5058
import org.jenkinsci.plugins.workflow.flow.FlowDefinitionDescriptor;
5159
import org.jenkinsci.plugins.workflow.flow.FlowExecution;
@@ -102,7 +110,7 @@ public SCMBinder(String scriptPath) {
102110
SCM scm;
103111
if (tip != null) {
104112
build.addAction(new SCMRevisionAction(scmSource, tip));
105-
SCMRevision rev = scmSource.getTrustedRevision(tip, listener);
113+
SCMRevision rev = getTrustedRevision(scmSource, tip, listener, build);
106114
try (SCMFileSystem fs = USE_HEAVYWEIGHT_CHECKOUT ? null : SCMFileSystem.of(scmSource, head, rev)) {
107115
if (fs != null) { // JENKINS-33273
108116
String script = null;
@@ -143,6 +151,24 @@ public SCMBinder(String scriptPath) {
143151
return new CpsScmFlowDefinition(scm, scriptPath).create(handle, listener, actions);
144152
}
145153

154+
private static Set<Class<? extends Cause>> passiveCauses = Set.of(
155+
BranchIndexingCause.class,
156+
BranchEventCause.class,
157+
SCMTrigger.SCMTriggerCause.class,
158+
TimerTrigger.TimerTriggerCause.class);
159+
/**
160+
* Like {@link SCMSource#getTrustedRevision} but only for builds with known passive triggers such as {@link BranchIndexingCause}.
161+
* Other causes such as {@link Cause.UserIdCause} or {@link ReplayCause} or {@code CheckRunGHEventSubscriber.GitHubChecksRerunActionCause}
162+
* are assumed trusted and so the tip revision is returned as is without consulting the SCM.
163+
*/
164+
static SCMRevision getTrustedRevision(SCMSource source, SCMRevision revision, TaskListener listener, Run<?, ?> build) throws IOException, InterruptedException {
165+
if (build.getCauses().stream().anyMatch(c -> passiveCauses.stream().anyMatch(t -> t.isInstance(c)))) {
166+
return source.getTrustedRevision(revision, listener);
167+
} else {
168+
return revision;
169+
}
170+
}
171+
146172
@Extension public static class DescriptorImpl extends FlowDefinitionDescriptor {
147173

148174
@NonNull

src/test/java/org/jenkinsci/plugins/workflow/multibranch/SCMBinderTest.java

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import com.cloudbees.hudson.plugins.folder.computed.DefaultOrphanedItemStrategy;
2828
import edu.umd.cs.findbugs.annotations.NonNull;
2929
import hudson.Util;
30+
import hudson.model.Cause;
31+
import hudson.model.CauseAction;
3032
import hudson.model.Item;
3133
import hudson.model.Result;
3234
import hudson.model.TaskListener;
@@ -235,6 +237,7 @@ public static void assertRevisionAction(WorkflowRun build) {
235237
assertEquals(1, mp.getItems().size());
236238
}
237239

240+
@Issue("JENKINS-46795")
238241
@Test public void untrustedRevisions() throws Exception {
239242
sampleGitRepo.init();
240243
String masterJenkinsfile = "node {checkout scm; echo readFile('file')}";
@@ -255,7 +258,6 @@ public static void assertRevisionAction(WorkflowRun build) {
255258
String branch = "some-other-branch-from-Norway";
256259
sampleGitRepo.git("checkout", "-b", branch);
257260
sampleGitRepo.write("Jenkinsfile", "error 'ALL YOUR BUILD STEPS ARE BELONG TO US'");
258-
sampleGitRepo.write("file", "subsequent content");
259261
sampleGitRepo.git("commit", "--all", "--message=big evil laugh");
260262
p = WorkflowMultiBranchProjectTest.scheduleAndFindBranchProject(mp, branch);
261263
r.waitUntilNoActivity();
@@ -268,17 +270,42 @@ public static void assertRevisionAction(WorkflowRun build) {
268270
r.assertLogContains("not trusting", b);
269271
SCMBinder.IGNORE_UNTRUSTED_EDITS = true;
270272
try {
271-
b = r.buildAndAssertSuccess(p);
273+
sampleGitRepo.write("file", "subsequent content");
274+
sampleGitRepo.git("commit", "--all", "--message=edits");
275+
p = WorkflowMultiBranchProjectTest.scheduleAndFindBranchProject(mp, branch);
276+
r.waitUntilNoActivity();
277+
b = p.getLastBuild();
278+
assertNotNull(b);
279+
assertEquals(2, b.getNumber());
272280
r.assertLogContains("subsequent content", b);
273281
r.assertLogContains("not trusting", b);
274282
} finally {
275283
SCMBinder.IGNORE_UNTRUSTED_EDITS = false;
276284
}
277285
sampleGitRepo.write("Jenkinsfile", masterJenkinsfile);
278286
sampleGitRepo.git("commit", "--all", "--message=meekly submitting");
279-
b = r.buildAndAssertSuccess(p);
287+
p = WorkflowMultiBranchProjectTest.scheduleAndFindBranchProject(mp, branch);
288+
r.waitUntilNoActivity();
289+
b = p.getLastBuild();
290+
assertNotNull(b);
291+
assertEquals(3, b.getNumber());
280292
r.assertLogContains("subsequent content", b);
281293
r.assertLogContains("not trusting", b);
294+
sampleGitRepo.write("Jenkinsfile", "node {checkout scm; echo readTrusted('file').toUpperCase()}");
295+
sampleGitRepo.git("commit", "--all", "--message=changes to be approved");
296+
p = WorkflowMultiBranchProjectTest.scheduleAndFindBranchProject(mp, branch);
297+
r.waitUntilNoActivity();
298+
b = p.getLastBuild();
299+
assertNotNull(b);
300+
assertEquals(4, b.getNumber());
301+
r.assertBuildStatus(Result.FAILURE, b);
302+
r.assertLogContains(Messages.ReadTrustedStep__has_been_modified_in_an_untrusted_revis("Jenkinsfile"), b);
303+
r.assertLogContains("not trusting", b);
304+
b = p.scheduleBuild2(0, new CauseAction(new Cause.UserIdCause())).get();
305+
assertEquals(5, b.getNumber());
306+
r.assertBuildStatusSuccess(b);
307+
r.assertLogContains("SUBSEQUENT CONTENT", b);
308+
r.assertLogNotContains("not trusting", b);
282309
}
283310
public static class WarySource extends GitSCMSource {
284311
public WarySource(String id, String remote, String credentialsId, String includes, String excludes, boolean ignoreOnPushNotifications) {

0 commit comments

Comments
 (0)