-
Notifications
You must be signed in to change notification settings - Fork 129
[JENKINS-46795] Abort builds with untrusted Jenkinsfile, but only given passive cause
#220
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 4 commits
1d80e65
84b1852
9f63e0d
18291d7
e0cc54b
3e58055
7b5acb2
2c7bebf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,29 +25,36 @@ | |
| package org.jenkinsci.plugins.workflow.multibranch; | ||
|
|
||
| import edu.umd.cs.findbugs.annotations.NonNull; | ||
| import hudson.AbortException; | ||
| import hudson.Extension; | ||
| import hudson.Functions; | ||
| import hudson.MarkupText; | ||
| import hudson.console.ConsoleAnnotationDescriptor; | ||
| import hudson.console.ConsoleAnnotator; | ||
| import hudson.console.ConsoleNote; | ||
| import hudson.model.Action; | ||
| import hudson.model.Cause; | ||
| import hudson.model.Descriptor; | ||
| import hudson.model.DescriptorVisibilityFilter; | ||
| import hudson.model.ItemGroup; | ||
| import hudson.model.Queue; | ||
| import hudson.model.Result; | ||
| import hudson.model.Run; | ||
| import hudson.model.TaskListener; | ||
| import hudson.scm.SCM; | ||
| import hudson.triggers.SCMTrigger; | ||
| import hudson.triggers.TimerTrigger; | ||
| import java.io.IOException; | ||
| import java.util.List; | ||
| import java.util.Set; | ||
| import jenkins.branch.Branch; | ||
| import jenkins.branch.BranchEventCause; | ||
| import jenkins.branch.BranchIndexingCause; | ||
| import jenkins.scm.api.SCMFileSystem; | ||
| import jenkins.scm.api.SCMHead; | ||
| import jenkins.scm.api.SCMRevision; | ||
| import jenkins.scm.api.SCMRevisionAction; | ||
| import jenkins.scm.api.SCMSource; | ||
| import jenkins.util.SystemProperties; | ||
| import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; | ||
| import org.jenkinsci.plugins.workflow.cps.CpsScmFlowDefinition; | ||
| import org.jenkinsci.plugins.workflow.cps.replay.ReplayCause; | ||
| import org.jenkinsci.plugins.workflow.flow.FlowDefinition; | ||
| import org.jenkinsci.plugins.workflow.flow.FlowDefinitionDescriptor; | ||
| import org.jenkinsci.plugins.workflow.flow.FlowExecution; | ||
|
|
@@ -61,7 +68,11 @@ | |
| class SCMBinder extends FlowDefinition { | ||
|
|
||
| /** Kill switch for JENKINS-33273 in case of problems. */ | ||
| static /* not final */ boolean USE_HEAVYWEIGHT_CHECKOUT = Boolean.getBoolean(SCMBinder.class.getName() + ".USE_HEAVYWEIGHT_CHECKOUT"); // TODO 2.4+ use SystemProperties | ||
| static /* not final */ boolean USE_HEAVYWEIGHT_CHECKOUT = SystemProperties.getBoolean(SCMBinder.class.getName() + ".USE_HEAVYWEIGHT_CHECKOUT"); | ||
|
|
||
| /** Kill switch for making this as strict as {@link ReadTrustedStep} about untrusted modifications. */ | ||
| static /* not final */ boolean IGNORE_UNTRUSTED_EDITS = SystemProperties.getBoolean(SCMBinder.class.getName() + ".IGNORE_UNTRUSTED_EDITS"); | ||
|
|
||
| private String scriptPath = WorkflowBranchProjectFactory.SCRIPT; | ||
|
|
||
| public Object readResolve() { | ||
|
|
@@ -100,7 +111,7 @@ public SCMBinder(String scriptPath) { | |
| SCM scm; | ||
| if (tip != null) { | ||
| build.addAction(new SCMRevisionAction(scmSource, tip)); | ||
| SCMRevision rev = scmSource.getTrustedRevision(tip, listener); | ||
| SCMRevision rev = getTrustedRevision(scmSource, tip, listener, build); | ||
| try (SCMFileSystem fs = USE_HEAVYWEIGHT_CHECKOUT ? null : SCMFileSystem.of(scmSource, head, rev)) { | ||
| if (fs != null) { // JENKINS-33273 | ||
| String script = null; | ||
|
|
@@ -111,10 +122,10 @@ public SCMBinder(String scriptPath) { | |
| listener.error("Could not do lightweight checkout, falling back to heavyweight").println(Functions.printThrowable(x).trim()); | ||
| } | ||
| if (script != null) { | ||
| if (!rev.equals(tip)) { | ||
| // Print a warning in builds where an untrusted contributor has tried to edit Jenkinsfile. | ||
| // If we fail to check this (e.g., due to heavyweight checkout), a warning will still be printed to the log | ||
| // by the SCM, but that is less apparent. | ||
| if (!IGNORE_UNTRUSTED_EDITS && !rev.equals(tip)) { | ||
| // Make a best effort to abort builds where an untrusted contributor has tried to edit Jenkinsfile. | ||
| // If we fail to check this (e.g., due to heavyweight checkout), a warning will be printed to the log | ||
| // and the build will continue with the trusted variant, which is safe but confusing. | ||
| SCMFileSystem tipFS = SCMFileSystem.of(scmSource, head, tip); | ||
| if (tipFS != null) { | ||
| String tipScript = null; | ||
|
|
@@ -124,9 +135,8 @@ public SCMBinder(String scriptPath) { | |
| listener.error("Could not compare lightweight checkout of trusted revision").println(Functions.printThrowable(x).trim()); | ||
| } | ||
| if (tipScript != null && !script.equals(tipScript)) { | ||
| listener.annotate(new WarningNote()); | ||
| listener.getLogger().println(Messages.ReadTrustedStep__has_been_modified_in_an_untrusted_revis(scriptPath)); | ||
| // TODO JENKINS-45970 consider aborting instead, at least optionally | ||
| build.setResult(Result.NOT_BUILT); | ||
| throw new AbortException(Messages.ReadTrustedStep__has_been_modified_in_an_untrusted_revis(scriptPath)); | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -143,6 +153,24 @@ public SCMBinder(String scriptPath) { | |
| return new CpsScmFlowDefinition(scm, scriptPath).create(handle, listener, actions); | ||
| } | ||
|
|
||
| private static Set<Class<? extends Cause>> passiveCauses = Set.of( | ||
| BranchIndexingCause.class, | ||
| BranchEventCause.class, | ||
| SCMTrigger.SCMTriggerCause.class, | ||
| TimerTrigger.TimerTriggerCause.class); | ||
| /** | ||
| * Like {@link SCMSource#getTrustedRevision} but only for builds with known passive triggers such as {@link BranchIndexingCause}. | ||
| * Other causes such as {@link Cause.UserIdCause} or {@link ReplayCause} or {@code CheckRunGHEventSubscriber.GitHubChecksRerunActionCause} | ||
| * are assumed trusted and so the tip revision is returned as is without consulting the SCM. | ||
| */ | ||
| static SCMRevision getTrustedRevision(SCMSource source, SCMRevision revision, TaskListener listener, Run<?, ?> build) throws IOException, InterruptedException { | ||
| if (build.getCauses().stream().anyMatch(c -> passiveCauses.stream().anyMatch(t -> t.isInstance(c)))) { | ||
| return source.getTrustedRevision(revision, listener); | ||
| } else { | ||
| return revision; | ||
| } | ||
| } | ||
|
|
||
| @Extension public static class DescriptorImpl extends FlowDefinitionDescriptor { | ||
|
|
||
| @NonNull | ||
|
|
@@ -165,22 +193,4 @@ public SCMBinder(String scriptPath) { | |
|
|
||
| } | ||
|
|
||
| // TODO seems there is no general-purpose ConsoleNote which simply wraps markup in specified HTML | ||
| @SuppressWarnings("rawtypes") | ||
| public static class WarningNote extends ConsoleNote { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do not see why; it was not used elsewhere to begin with.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nvm, misread the diff since I didn't expect removal here.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That said, removing this class might cause trouble with old build logs containing non-deserializable annotations? Or is this going somewhere else? Or have you tested it and it'll just silently drop them in build logs nobody cares about anyway given the early abort?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://github.com/jenkinsci/jenkins/blob/aa83562568b44aa85ff17bdfab831bbe39c2fd15/core/src/main/java/hudson/console/ConsoleAnnotationOutputStream.java#L112-L125 should just drop the annotation, i.e. make the text be plain rather than yellow. Not specifically tested. |
||
|
|
||
| @Override public ConsoleAnnotator annotate(Object context, MarkupText text, int charPos) { | ||
| text.addMarkup(0, text.length(), "<span class='warning-inline'>", "</span>"); | ||
| return null; | ||
| } | ||
|
|
||
| @Extension public static final class DescriptorImpl extends ConsoleAnnotationDescriptor { | ||
| @NonNull | ||
| @Override public String getDisplayName() { | ||
| return "Multibranch warnings"; | ||
| } | ||
| } | ||
|
|
||
| } | ||
|
|
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.